1use std::collections::BTreeMap;
2use std::ffi::c_char;
3use std::ops::{BitOr, BitOrAssign};
4use std::path::{Path, PathBuf};
5
6use crate::error::CloudKitError;
7use crate::ffi;
8use crate::private::{
9 cstring_from_str, error_from_status, parse_json_ptr, CKAssetPayload, CKRecordIDPayload,
10 CKRecordPayload, CKRecordValuePayload, CKRecordZoneIDPayload, CKRecordZonePayload,
11 CKReferencePayload, RecordValueKind,
12};
13use crate::reference_utility::{CKReference, CKReferenceAction};
14
15const DEFAULT_ZONE_NAME: &str = "_defaultZone";
16const DEFAULT_OWNER_NAME: &str = "__defaultOwner__";
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
20pub struct CKRecordZoneCapabilities(u64);
21
22impl CKRecordZoneCapabilities {
23 pub const FETCH_CHANGES: Self = Self(1 << 0);
25 pub const ATOMIC: Self = Self(1 << 1);
27 pub const SHARING: Self = Self(1 << 2);
29 pub const ZONE_WIDE_SHARING: Self = Self(1 << 3);
31
32 pub const fn new(bits: u64) -> Self {
34 Self(bits)
35 }
36
37 pub const fn bits(self) -> u64 {
39 self.0
40 }
41
42 pub const fn contains(self, other: Self) -> bool {
44 (self.0 & other.0) == other.0
45 }
46}
47
48impl BitOr for CKRecordZoneCapabilities {
49 type Output = Self;
50
51 fn bitor(self, rhs: Self) -> Self::Output {
52 Self(self.0 | rhs.0)
53 }
54}
55
56impl BitOrAssign for CKRecordZoneCapabilities {
57 fn bitor_assign(&mut self, rhs: Self) {
58 self.0 |= rhs.0;
59 }
60}
61
62#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
64#[non_exhaustive]
65pub enum CKRecordZoneEncryptionScope {
66 PerRecord,
68 PerZone,
70 Unknown(i32),
72}
73
74impl CKRecordZoneEncryptionScope {
75 pub(crate) const fn from_raw(raw: i32) -> Self {
76 match raw {
77 0 => Self::PerRecord,
78 1 => Self::PerZone,
79 other => Self::Unknown(other),
80 }
81 }
82
83 pub(crate) const fn to_raw(self) -> i32 {
84 match self {
85 Self::PerRecord => 0,
86 Self::PerZone => 1,
87 Self::Unknown(raw) => raw,
88 }
89 }
90}
91
92#[derive(Debug, Clone, PartialEq, Eq, Hash)]
94pub struct CKRecordZoneID {
95 zone_name: String,
96 owner_name: String,
97}
98
99impl CKRecordZoneID {
100 pub fn new(zone_name: impl Into<String>, owner_name: impl Into<String>) -> Self {
102 Self {
103 zone_name: zone_name.into(),
104 owner_name: owner_name.into(),
105 }
106 }
107
108 pub fn default_zone() -> Self {
110 Self::new(DEFAULT_ZONE_NAME, DEFAULT_OWNER_NAME)
111 }
112
113 pub fn zone_name(&self) -> &str {
115 &self.zone_name
116 }
117
118 pub fn owner_name(&self) -> &str {
120 &self.owner_name
121 }
122
123 pub(crate) fn from_payload(payload: CKRecordZoneIDPayload) -> Self {
124 Self::new(payload.zone_name, payload.owner_name)
125 }
126
127 pub(crate) fn to_payload(&self) -> CKRecordZoneIDPayload {
128 CKRecordZoneIDPayload {
129 zone_name: self.zone_name.clone(),
130 owner_name: self.owner_name.clone(),
131 }
132 }
133}
134
135#[derive(Debug, Clone, PartialEq, Eq, Hash)]
137pub struct CKRecordID {
138 record_name: String,
139 zone_id: CKRecordZoneID,
140}
141
142impl CKRecordID {
143 pub fn new(record_name: impl Into<String>) -> Self {
145 Self::with_zone(record_name, CKRecordZoneID::default_zone())
146 }
147
148 pub fn with_zone(record_name: impl Into<String>, zone_id: CKRecordZoneID) -> Self {
150 Self {
151 record_name: record_name.into(),
152 zone_id,
153 }
154 }
155
156 pub fn record_name(&self) -> &str {
158 &self.record_name
159 }
160
161 pub fn zone_id(&self) -> &CKRecordZoneID {
163 &self.zone_id
164 }
165
166 pub(crate) fn from_payload(payload: CKRecordIDPayload) -> Self {
167 Self::with_zone(
168 payload.record_name,
169 CKRecordZoneID::from_payload(payload.zone_id),
170 )
171 }
172
173 pub(crate) fn to_payload(&self) -> CKRecordIDPayload {
174 CKRecordIDPayload {
175 record_name: self.record_name.clone(),
176 zone_id: self.zone_id.to_payload(),
177 }
178 }
179}
180
181#[derive(Debug, Clone, PartialEq, Eq, Hash)]
183pub struct CKAsset {
184 file_url: PathBuf,
185}
186
187impl CKAsset {
188 pub fn new(path: impl Into<PathBuf>) -> Self {
190 Self {
191 file_url: path.into(),
192 }
193 }
194
195 pub fn file_url(&self) -> &Path {
197 &self.file_url
198 }
199
200 pub(crate) fn from_payload(payload: CKAssetPayload) -> Self {
201 Self::new(payload.file_url)
202 }
203
204 pub(crate) fn to_payload(&self) -> CKAssetPayload {
205 CKAssetPayload {
206 file_url: self.file_url.to_string_lossy().into_owned(),
207 }
208 }
209}
210
211#[derive(Debug, Clone, PartialEq)]
213#[non_exhaustive]
214pub enum RecordValue {
215 String(String),
217 Int(i64),
219 Double(f64),
221 Bool(bool),
223 Bytes(Vec<u8>),
225 Date(String),
227 Asset(CKAsset),
229 Reference(CKReference),
231 Array(Vec<RecordValue>),
233}
234
235impl From<String> for RecordValue {
236 fn from(value: String) -> Self {
237 Self::String(value)
238 }
239}
240
241impl From<&str> for RecordValue {
242 fn from(value: &str) -> Self {
243 Self::String(value.into())
244 }
245}
246
247impl From<bool> for RecordValue {
248 fn from(value: bool) -> Self {
249 Self::Bool(value)
250 }
251}
252
253impl From<i64> for RecordValue {
254 fn from(value: i64) -> Self {
255 Self::Int(value)
256 }
257}
258
259impl From<i32> for RecordValue {
260 fn from(value: i32) -> Self {
261 Self::Int(i64::from(value))
262 }
263}
264
265impl From<f64> for RecordValue {
266 fn from(value: f64) -> Self {
267 Self::Double(value)
268 }
269}
270
271impl From<Vec<u8>> for RecordValue {
272 fn from(value: Vec<u8>) -> Self {
273 Self::Bytes(value)
274 }
275}
276
277impl From<CKAsset> for RecordValue {
278 fn from(value: CKAsset) -> Self {
279 Self::Asset(value)
280 }
281}
282
283impl From<CKReference> for RecordValue {
284 fn from(value: CKReference) -> Self {
285 Self::Reference(value)
286 }
287}
288
289impl From<Vec<RecordValue>> for RecordValue {
290 fn from(value: Vec<RecordValue>) -> Self {
291 Self::Array(value)
292 }
293}
294
295impl RecordValue {
296 pub(crate) fn from_payload(payload: CKRecordValuePayload) -> Self {
297 match payload.kind {
298 RecordValueKind::String => Self::String(payload.string_value.unwrap_or_default()),
299 RecordValueKind::Int => Self::Int(payload.int_value.unwrap_or_default()),
300 RecordValueKind::Double => Self::Double(payload.double_value.unwrap_or_default()),
301 RecordValueKind::Bool => Self::Bool(payload.bool_value.unwrap_or_default()),
302 RecordValueKind::Bytes => Self::Bytes(payload.bytes_value.unwrap_or_default()),
303 RecordValueKind::Date => Self::Date(payload.date_value.unwrap_or_default()),
304 RecordValueKind::Asset => Self::Asset(CKAsset::from_payload(
305 payload.asset_value.unwrap_or(CKAssetPayload {
306 file_url: String::new(),
307 }),
308 )),
309 RecordValueKind::Reference => Self::Reference(CKReference::from_payload(
310 payload
311 .reference_value
312 .unwrap_or_else(|| CKReferencePayload {
313 record_id: CKRecordID::new(String::new()).to_payload(),
314 action: CKReferenceAction::None.to_raw(),
315 }),
316 )),
317 RecordValueKind::Array => Self::Array(
318 payload
319 .array_value
320 .unwrap_or_default()
321 .into_iter()
322 .map(Self::from_payload)
323 .collect(),
324 ),
325 }
326 }
327
328 pub(crate) fn to_payload(&self) -> CKRecordValuePayload {
329 match self {
330 Self::String(value) => CKRecordValuePayload {
331 kind: RecordValueKind::String,
332 string_value: Some(value.clone()),
333 int_value: None,
334 double_value: None,
335 bool_value: None,
336 bytes_value: None,
337 date_value: None,
338 asset_value: None,
339 reference_value: None,
340 array_value: None,
341 },
342 Self::Int(value) => CKRecordValuePayload {
343 kind: RecordValueKind::Int,
344 string_value: None,
345 int_value: Some(*value),
346 double_value: None,
347 bool_value: None,
348 bytes_value: None,
349 date_value: None,
350 asset_value: None,
351 reference_value: None,
352 array_value: None,
353 },
354 Self::Double(value) => CKRecordValuePayload {
355 kind: RecordValueKind::Double,
356 string_value: None,
357 int_value: None,
358 double_value: Some(*value),
359 bool_value: None,
360 bytes_value: None,
361 date_value: None,
362 asset_value: None,
363 reference_value: None,
364 array_value: None,
365 },
366 Self::Bool(value) => CKRecordValuePayload {
367 kind: RecordValueKind::Bool,
368 string_value: None,
369 int_value: None,
370 double_value: None,
371 bool_value: Some(*value),
372 bytes_value: None,
373 date_value: None,
374 asset_value: None,
375 reference_value: None,
376 array_value: None,
377 },
378 Self::Bytes(value) => CKRecordValuePayload {
379 kind: RecordValueKind::Bytes,
380 string_value: None,
381 int_value: None,
382 double_value: None,
383 bool_value: None,
384 bytes_value: Some(value.clone()),
385 date_value: None,
386 asset_value: None,
387 reference_value: None,
388 array_value: None,
389 },
390 Self::Date(value) => CKRecordValuePayload {
391 kind: RecordValueKind::Date,
392 string_value: None,
393 int_value: None,
394 double_value: None,
395 bool_value: None,
396 bytes_value: None,
397 date_value: Some(value.clone()),
398 asset_value: None,
399 reference_value: None,
400 array_value: None,
401 },
402 Self::Asset(asset) => CKRecordValuePayload {
403 kind: RecordValueKind::Asset,
404 string_value: None,
405 int_value: None,
406 double_value: None,
407 bool_value: None,
408 bytes_value: None,
409 date_value: None,
410 asset_value: Some(asset.to_payload()),
411 reference_value: None,
412 array_value: None,
413 },
414 Self::Reference(reference) => CKRecordValuePayload {
415 kind: RecordValueKind::Reference,
416 string_value: None,
417 int_value: None,
418 double_value: None,
419 bool_value: None,
420 bytes_value: None,
421 date_value: None,
422 asset_value: None,
423 reference_value: Some(reference.to_payload()),
424 array_value: None,
425 },
426 Self::Array(values) => CKRecordValuePayload {
427 kind: RecordValueKind::Array,
428 string_value: None,
429 int_value: None,
430 double_value: None,
431 bool_value: None,
432 bytes_value: None,
433 date_value: None,
434 asset_value: None,
435 reference_value: None,
436 array_value: Some(values.iter().map(Self::to_payload).collect()),
437 },
438 }
439 }
440}
441
442#[derive(Debug, Clone, PartialEq, Eq, Hash)]
444pub struct CKRecordZone {
445 zone_id: CKRecordZoneID,
446 capabilities: CKRecordZoneCapabilities,
447 share: Option<CKReference>,
448 encryption_scope: Option<CKRecordZoneEncryptionScope>,
449}
450
451impl CKRecordZone {
452 pub fn new(zone_name: impl Into<String>) -> Self {
454 Self {
455 zone_id: CKRecordZoneID::new(zone_name, DEFAULT_OWNER_NAME),
456 capabilities: CKRecordZoneCapabilities::default(),
457 share: None,
458 encryption_scope: Some(CKRecordZoneEncryptionScope::PerRecord),
459 }
460 }
461
462 pub fn with_zone_id(zone_id: CKRecordZoneID) -> Self {
464 Self {
465 zone_id,
466 capabilities: CKRecordZoneCapabilities::default(),
467 share: None,
468 encryption_scope: Some(CKRecordZoneEncryptionScope::PerRecord),
469 }
470 }
471
472 pub fn default_zone() -> Self {
474 Self::with_zone_id(CKRecordZoneID::default_zone())
475 }
476
477 pub fn zone_id(&self) -> &CKRecordZoneID {
479 &self.zone_id
480 }
481
482 pub const fn capabilities(&self) -> CKRecordZoneCapabilities {
484 self.capabilities
485 }
486
487 pub const fn share(&self) -> Option<&CKReference> {
489 self.share.as_ref()
490 }
491
492 pub const fn encryption_scope(&self) -> Option<CKRecordZoneEncryptionScope> {
494 self.encryption_scope
495 }
496
497 pub(crate) fn from_payload(payload: CKRecordZonePayload) -> Self {
498 Self {
499 zone_id: CKRecordZoneID::from_payload(payload.zone_id),
500 capabilities: CKRecordZoneCapabilities::new(payload.capabilities),
501 share: payload.share.map(CKReference::from_payload),
502 encryption_scope: payload
503 .encryption_scope
504 .map(CKRecordZoneEncryptionScope::from_raw),
505 }
506 }
507
508 pub(crate) fn to_payload(&self) -> CKRecordZonePayload {
509 CKRecordZonePayload {
510 zone_id: self.zone_id.to_payload(),
511 capabilities: self.capabilities.bits(),
512 share: self.share.as_ref().map(CKReference::to_payload),
513 encryption_scope: self
514 .encryption_scope
515 .map(CKRecordZoneEncryptionScope::to_raw),
516 }
517 }
518}
519
520#[derive(Debug, Clone, PartialEq)]
522pub struct CKRecord {
523 record_type: String,
524 record_id: CKRecordID,
525 fields: BTreeMap<String, RecordValue>,
526 encoded_system_fields: Vec<u8>,
527 record_change_tag: Option<String>,
528 creator_user_record_id: Option<CKRecordID>,
529 creation_date: Option<String>,
530 last_modified_user_record_id: Option<CKRecordID>,
531 modification_date: Option<String>,
532 parent: Option<CKReference>,
533 share: Option<CKReference>,
534 changed_keys: Vec<String>,
535 all_tokens: Vec<String>,
536}
537
538impl CKRecord {
539 pub fn new(record_type: &str) -> Result<Self, CloudKitError> {
541 let record_type = cstring_from_str(record_type, "record type")?;
542 let mut out_json: *mut c_char = core::ptr::null_mut();
543 let mut out_error: *mut c_char = core::ptr::null_mut();
544 let status =
545 unsafe { ffi::ck_record_create(record_type.as_ptr(), &mut out_json, &mut out_error) };
546 if status != ffi::status::OK {
547 return Err(unsafe { error_from_status(status, out_error) });
548 }
549 let payload: CKRecordPayload = unsafe { parse_json_ptr(out_json, "record")? };
550 Ok(Self::from_payload(payload))
551 }
552
553 pub fn with_record_id(record_type: impl Into<String>, record_id: CKRecordID) -> Self {
555 Self {
556 record_type: record_type.into(),
557 record_id,
558 fields: BTreeMap::new(),
559 encoded_system_fields: Vec::new(),
560 record_change_tag: None,
561 creator_user_record_id: None,
562 creation_date: None,
563 last_modified_user_record_id: None,
564 modification_date: None,
565 parent: None,
566 share: None,
567 changed_keys: Vec::new(),
568 all_tokens: Vec::new(),
569 }
570 }
571
572 pub fn with_zone(record_type: impl Into<String>, zone_id: CKRecordZoneID) -> Self {
574 Self::with_record_id(record_type, CKRecordID::with_zone(String::new(), zone_id))
575 }
576
577 pub fn record_type(&self) -> &str {
579 &self.record_type
580 }
581
582 pub fn record_id(&self) -> &CKRecordID {
584 &self.record_id
585 }
586
587 pub fn record_change_tag(&self) -> Option<&str> {
589 self.record_change_tag.as_deref()
590 }
591
592 pub const fn creator_user_record_id(&self) -> Option<&CKRecordID> {
594 self.creator_user_record_id.as_ref()
595 }
596
597 pub fn creation_date(&self) -> Option<&str> {
599 self.creation_date.as_deref()
600 }
601
602 pub const fn last_modified_user_record_id(&self) -> Option<&CKRecordID> {
604 self.last_modified_user_record_id.as_ref()
605 }
606
607 pub fn modification_date(&self) -> Option<&str> {
609 self.modification_date.as_deref()
610 }
611
612 pub const fn parent(&self) -> Option<&CKReference> {
614 self.parent.as_ref()
615 }
616
617 pub const fn share(&self) -> Option<&CKReference> {
619 self.share.as_ref()
620 }
621
622
623 pub fn changed_keys(&self) -> &[String] {
625 &self.changed_keys
626 }
627
628 pub fn all_tokens(&self) -> &[String] {
630 &self.all_tokens
631 }
632
633 pub fn object(&self, key: &str) -> Option<&RecordValue> {
635 self.fields.get(key)
636 }
637
638 pub fn set_object<V>(&mut self, key: &str, value: V)
640 where
641 V: Into<RecordValue>,
642 {
643 self.fields.insert(key.into(), value.into());
644 self.mark_changed_key(key);
645 }
646
647 pub fn remove_object(&mut self, key: &str) -> Option<RecordValue> {
649 let removed = self.fields.remove(key);
650 if removed.is_some() {
651 self.mark_changed_key(key);
652 }
653 removed
654 }
655
656 pub fn all_keys(&self) -> Vec<String> {
658 self.fields.keys().cloned().collect()
659 }
660
661 pub fn encoded_system_fields(&self) -> &[u8] {
663 &self.encoded_system_fields
664 }
665
666 pub fn set_parent_reference(&mut self, reference: CKReference) {
668 self.parent = Some(reference);
669 }
670
671 pub fn set_parent_reference_from_record(&mut self, parent_record: &CKRecord) {
673 self.set_parent_reference(CKReference::new(
674 parent_record.record_id().clone(),
675 CKReferenceAction::None,
676 ));
677 }
678
679 pub fn set_parent_reference_from_record_id(&mut self, parent_record_id: CKRecordID) {
681 self.set_parent_reference(CKReference::new(parent_record_id, CKReferenceAction::None));
682 }
683
684 pub fn clear_parent_reference(&mut self) {
686 self.parent = None;
687 }
688
689 fn mark_changed_key(&mut self, key: &str) {
690 if !self.changed_keys.iter().any(|changed| changed == key) {
691 self.changed_keys.push(key.into());
692 }
693 }
694
695 pub(crate) fn from_payload(payload: CKRecordPayload) -> Self {
696 Self {
697 record_type: payload.record_type,
698 record_id: CKRecordID::from_payload(payload.record_id),
699 fields: payload
700 .fields
701 .into_iter()
702 .map(|(key, value)| (key, RecordValue::from_payload(value)))
703 .collect(),
704 encoded_system_fields: payload.encoded_system_fields,
705 record_change_tag: payload.record_change_tag,
706 creator_user_record_id: payload.creator_user_record_id.map(CKRecordID::from_payload),
707 creation_date: payload.creation_date,
708 last_modified_user_record_id: payload
709 .last_modified_user_record_id
710 .map(CKRecordID::from_payload),
711 modification_date: payload.modification_date,
712 parent: payload.parent.map(CKReference::from_payload),
713 share: payload.share.map(CKReference::from_payload),
714 changed_keys: payload.changed_keys,
715 all_tokens: payload.all_tokens,
716 }
717 }
718
719 pub(crate) fn to_payload(&self) -> CKRecordPayload {
720 CKRecordPayload {
721 record_type: self.record_type.clone(),
722 record_id: self.record_id.to_payload(),
723 fields: self
724 .fields
725 .iter()
726 .map(|(key, value)| (key.clone(), value.to_payload()))
727 .collect(),
728 encoded_system_fields: self.encoded_system_fields.clone(),
729 record_change_tag: self.record_change_tag.clone(),
730 creator_user_record_id: self
731 .creator_user_record_id
732 .as_ref()
733 .map(CKRecordID::to_payload),
734 creation_date: self.creation_date.clone(),
735 last_modified_user_record_id: self
736 .last_modified_user_record_id
737 .as_ref()
738 .map(CKRecordID::to_payload),
739 modification_date: self.modification_date.clone(),
740 parent: self.parent.as_ref().map(CKReference::to_payload),
741 share: self.share.as_ref().map(CKReference::to_payload),
742 changed_keys: self.changed_keys.clone(),
743 all_tokens: self.all_tokens.clone(),
744 }
745 }
746}
747
748pub trait CKRecordKeyValueSetting {
750 fn object_for_key(&self, key: &str) -> Option<&RecordValue>;
752 fn set_object_for_key<V>(&mut self, key: &str, value: V)
754 where
755 V: Into<RecordValue>;
756 fn object_for_keyed_subscript(&self, key: &str) -> Option<&RecordValue>;
758 fn set_object_for_keyed_subscript<V>(&mut self, key: &str, value: V)
760 where
761 V: Into<RecordValue>;
762 fn all_keys(&self) -> Vec<String>;
764 fn changed_keys(&self) -> &[String];
766}
767
768impl CKRecordKeyValueSetting for CKRecord {
769 fn object_for_key(&self, key: &str) -> Option<&RecordValue> {
770 self.object(key)
771 }
772
773 fn set_object_for_key<V>(&mut self, key: &str, value: V)
774 where
775 V: Into<RecordValue>,
776 {
777 self.set_object(key, value);
778 }
779
780 fn object_for_keyed_subscript(&self, key: &str) -> Option<&RecordValue> {
781 self.object(key)
782 }
783
784 fn set_object_for_keyed_subscript<V>(&mut self, key: &str, value: V)
785 where
786 V: Into<RecordValue>,
787 {
788 self.set_object(key, value);
789 }
790
791 fn all_keys(&self) -> Vec<String> {
792 self.all_keys()
793 }
794
795 fn changed_keys(&self) -> &[String] {
796 self.changed_keys()
797 }
798}
799
800#[cfg(test)]
801mod tests {
802 use super::*;
803
804 #[test]
805 fn zone_capabilities_support_bit_operations() {
806 let mut capabilities =
807 CKRecordZoneCapabilities::FETCH_CHANGES | CKRecordZoneCapabilities::SHARING;
808
809 assert_eq!(capabilities.bits(), 5);
810 assert!(capabilities.contains(CKRecordZoneCapabilities::FETCH_CHANGES));
811 assert!(capabilities.contains(CKRecordZoneCapabilities::SHARING));
812 assert!(!capabilities.contains(CKRecordZoneCapabilities::ATOMIC));
813
814 capabilities |= CKRecordZoneCapabilities::ATOMIC;
815
816 assert!(capabilities.contains(CKRecordZoneCapabilities::ATOMIC));
817 assert_eq!(capabilities.bits(), 7);
818 }
819
820 #[test]
821 fn encryption_scope_round_trips_known_and_unknown_values() {
822 assert_eq!(
823 CKRecordZoneEncryptionScope::from_raw(0),
824 CKRecordZoneEncryptionScope::PerRecord
825 );
826 assert_eq!(
827 CKRecordZoneEncryptionScope::from_raw(1),
828 CKRecordZoneEncryptionScope::PerZone
829 );
830 assert_eq!(
831 CKRecordZoneEncryptionScope::from_raw(42),
832 CKRecordZoneEncryptionScope::Unknown(42)
833 );
834 assert_eq!(CKRecordZoneEncryptionScope::PerZone.to_raw(), 1);
835 assert_eq!(CKRecordZoneEncryptionScope::Unknown(42).to_raw(), 42);
836 }
837
838 #[test]
839 fn zone_ids_round_trip_and_default_zone_matches_constants() {
840 let default_zone = CKRecordZoneID::default_zone();
841 let zone = CKRecordZoneID::new("CustomZone", "owner-1");
842
843 assert_eq!(default_zone.zone_name(), "_defaultZone");
844 assert_eq!(default_zone.owner_name(), "__defaultOwner__");
845 assert_eq!(CKRecordZoneID::from_payload(zone.to_payload()), zone);
846 }
847
848 #[test]
849 fn record_ids_round_trip_with_custom_zone() {
850 let zone = CKRecordZoneID::new("Photos", "owner-2");
851 let record_id = CKRecordID::with_zone("record-1", zone.clone());
852
853 assert_eq!(record_id.record_name(), "record-1");
854 assert_eq!(record_id.zone_id(), &zone);
855 assert_eq!(CKRecordID::from_payload(record_id.to_payload()), record_id);
856 }
857
858 #[test]
859 fn assets_round_trip_payloads() {
860 let asset = CKAsset::new("cover.heic");
861
862 assert_eq!(CKAsset::from_payload(asset.to_payload()), asset);
863 }
864
865 #[test]
866 fn record_values_round_trip_nested_payloads() {
867 let record_value = RecordValue::Array(vec![
868 RecordValue::from("headline"),
869 RecordValue::from(42_i32),
870 RecordValue::from(true),
871 RecordValue::Reference(CKReference::delete_self(CKRecordID::new("child"))),
872 RecordValue::from(CKAsset::new("cover.heic")),
873 ]);
874
875 assert_eq!(RecordValue::from_payload(record_value.to_payload()), record_value);
876 }
877
878 #[test]
879 fn records_with_zone_start_with_expected_defaults() {
880 let zone = CKRecordZoneID::new("Drafts", "owner-3");
881 let record = CKRecord::with_zone("Photo", zone.clone());
882
883 assert_eq!(record.record_type(), "Photo");
884 assert_eq!(record.record_id().record_name(), "");
885 assert_eq!(record.record_id().zone_id(), &zone);
886 assert!(record.record_change_tag().is_none());
887 assert!(record.creator_user_record_id().is_none());
888 assert!(record.creation_date().is_none());
889 assert!(record.last_modified_user_record_id().is_none());
890 assert!(record.modification_date().is_none());
891 assert!(record.parent().is_none());
892 assert!(record.share().is_none());
893 }
894}