1use core::fmt;
5use core::fmt::Debug;
6use core::fmt::Display;
7use identity_credential::credential::Jws;
8use identity_did::CoreDID;
9use identity_did::DIDUrl;
10use identity_document::verifiable::JwsVerificationOptions;
11use identity_verification::jose::jws::DecodedJws;
12use identity_verification::jose::jws::JwsVerifier;
13use product_common::network_name::NetworkName;
14use serde::Deserialize;
15use serde::Serialize;
16
17use identity_core::common::Object;
18use identity_core::common::OneOrSet;
19use identity_core::common::OrderedSet;
20use identity_core::common::Url;
21use identity_core::convert::FmtJson;
22use identity_document::document::CoreDocument;
23use identity_document::service::Service;
24use identity_document::utils::DIDUrlQuery;
25use identity_verification::MethodRelationship;
26use identity_verification::MethodScope;
27use identity_verification::VerificationMethod;
28
29use crate::error::Result;
30use crate::Error;
31use crate::IotaDID;
32use crate::IotaDocumentMetadata;
33use crate::StateMetadataDocument;
34use crate::StateMetadataEncoding;
35
36#[derive(Debug, Deserialize)]
38struct ProvisionalIotaDocument {
39 #[serde(rename = "doc")]
40 document: CoreDocument,
41 #[serde(rename = "meta")]
42 metadata: IotaDocumentMetadata,
43}
44
45impl TryFrom<ProvisionalIotaDocument> for IotaDocument {
46 type Error = Error;
47 fn try_from(provisional: ProvisionalIotaDocument) -> std::result::Result<Self, Self::Error> {
48 let ProvisionalIotaDocument { document, metadata } = provisional;
49
50 IotaDID::check_validity(document.id()).map_err(|_| {
51 Error::SerializationError(
52 "deserializing iota document failed: id does not conform to the IOTA method specification",
53 None,
54 )
55 })?;
56
57 for controller_id in document
58 .controller()
59 .map(|controller_set| controller_set.iter())
60 .into_iter()
61 .flatten()
62 {
63 IotaDID::check_validity(controller_id).map_err(|_| {
64 Error::SerializationError(
65 "deserializing iota document failed: controller not conforming to the iota method specification detected",
66 None,
67 )
68 })?;
69 }
70
71 Ok(IotaDocument { document, metadata })
72 }
73}
74
75#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
79#[serde(try_from = "ProvisionalIotaDocument")]
80pub struct IotaDocument {
81 #[serde(rename = "doc")]
83 pub(crate) document: CoreDocument,
84 #[serde(rename = "meta")]
86 pub metadata: IotaDocumentMetadata,
87}
88
89impl IotaDocument {
90 pub fn new(network: &NetworkName) -> Self {
98 Self::new_with_id(IotaDID::placeholder(network))
99 }
100
101 pub fn new_with_id(id: IotaDID) -> Self {
103 let document: CoreDocument = CoreDocument::builder(Object::default())
105 .id(id.into())
106 .build()
107 .expect("empty IotaDocument constructor failed");
108 let metadata: IotaDocumentMetadata = IotaDocumentMetadata::new();
109 Self { document, metadata }
110 }
111
112 pub fn id(&self) -> &IotaDID {
118 IotaDID::from_inner_ref_unchecked(self.document.id())
121 }
122
123 pub fn controller(&self) -> impl Iterator<Item = &IotaDID> + '_ {
125 let core_did_controller_iter = self
126 .document
127 .controller()
128 .map(|controllers| controllers.iter())
129 .into_iter()
130 .flatten();
131
132 core_did_controller_iter.map(IotaDID::from_inner_ref_unchecked)
134 }
135
136 pub fn set_controller<T>(&mut self, controller: T)
142 where
143 T: IntoIterator<Item = IotaDID>,
144 {
145 let controller_core_dids: Option<OneOrSet<CoreDID>> = {
146 let controller_set: OrderedSet<CoreDID> = controller.into_iter().map(CoreDID::from).collect();
147 if controller_set.is_empty() {
148 None
149 } else {
150 Some(OneOrSet::new_set(controller_set).expect("controller is checked to be not empty"))
151 }
152 };
153
154 *self.document.controller_mut() = controller_core_dids;
155 }
156
157 pub fn also_known_as(&self) -> &OrderedSet<Url> {
159 self.document.also_known_as()
160 }
161
162 pub fn also_known_as_mut(&mut self) -> &mut OrderedSet<Url> {
164 self.document.also_known_as_mut()
165 }
166
167 pub fn core_document(&self) -> &CoreDocument {
169 &self.document
170 }
171
172 pub(crate) fn core_document_mut(&mut self) -> &mut CoreDocument {
177 &mut self.document
178 }
179
180 pub fn properties(&self) -> &Object {
182 self.document.properties()
183 }
184
185 pub fn properties_mut_unchecked(&mut self) -> &mut Object {
193 self.document.properties_mut_unchecked()
194 }
195
196 pub fn service(&self) -> &OrderedSet<Service> {
202 self.document.service()
203 }
204
205 pub fn insert_service(&mut self, service: Service) -> Result<()> {
211 self
212 .core_document_mut()
213 .insert_service(service)
214 .map_err(Error::InvalidDoc)
215 }
216
217 pub fn remove_service(&mut self, did_url: &DIDUrl) -> Option<Service> {
221 self.core_document_mut().remove_service(did_url)
222 }
223
224 pub fn methods(&self, scope: Option<MethodScope>) -> Vec<&VerificationMethod> {
232 self.document.methods(scope)
233 }
234
235 pub fn insert_method(&mut self, method: VerificationMethod, scope: MethodScope) -> Result<()> {
241 self
242 .core_document_mut()
243 .insert_method(method, scope)
244 .map_err(Error::InvalidDoc)
245 }
246
247 pub fn remove_method(&mut self, did_url: &DIDUrl) -> Option<VerificationMethod> {
254 self.core_document_mut().remove_method(did_url)
255 }
256
257 pub fn remove_method_and_scope(&mut self, did_url: &DIDUrl) -> Option<(VerificationMethod, MethodScope)> {
265 self.core_document_mut().remove_method_and_scope(did_url)
266 }
267
268 pub fn attach_method_relationship<'query, Q>(
276 &mut self,
277 method_query: Q,
278 relationship: MethodRelationship,
279 ) -> Result<bool>
280 where
281 Q: Into<DIDUrlQuery<'query>>,
282 {
283 self
284 .core_document_mut()
285 .attach_method_relationship(method_query, relationship)
286 .map_err(Error::InvalidDoc)
287 }
288
289 pub fn detach_method_relationship<'query, Q>(
302 &mut self,
303 method_query: Q,
304 relationship: MethodRelationship,
305 ) -> Result<bool>
306 where
307 Q: Into<DIDUrlQuery<'query>>,
308 {
309 self
310 .core_document_mut()
311 .detach_method_relationship(method_query, relationship)
312 .map_err(Error::InvalidDoc)
313 }
314
315 pub fn resolve_method_mut<'query, Q>(
322 &mut self,
323 method_query: Q,
324 scope: Option<MethodScope>,
325 ) -> Option<&mut VerificationMethod>
326 where
327 Q: Into<DIDUrlQuery<'query>>,
328 {
329 self.document.resolve_method_mut(method_query, scope)
330 }
331
332 pub fn resolve_service<'query, 'me, Q>(&'me self, service_query: Q) -> Option<&'me Service>
336 where
337 Q: Into<DIDUrlQuery<'query>>,
338 {
339 self.document.resolve_service(service_query)
340 }
341
342 pub fn resolve_method<'query, 'me, Q>(
347 &'me self,
348 method_query: Q,
349 scope: Option<MethodScope>,
350 ) -> Option<&'me VerificationMethod>
351 where
352 Q: Into<DIDUrlQuery<'query>>,
353 {
354 self.document.resolve_method(method_query, scope)
355 }
356
357 pub fn verify_jws<'jws, T: JwsVerifier>(
369 &self,
370 jws: &'jws Jws,
371 detached_payload: Option<&'jws [u8]>,
372 signature_verifier: &T,
373 options: &JwsVerificationOptions,
374 ) -> Result<DecodedJws<'jws>> {
375 self
376 .core_document()
377 .verify_jws(jws.as_str(), detached_payload, signature_verifier, options)
378 .map_err(Error::JwsVerificationError)
379 }
380
381 pub fn pack(self) -> Result<Vec<u8>> {
388 self.pack_with_encoding(StateMetadataEncoding::default())
389 }
390
391 pub fn pack_with_encoding(self, encoding: StateMetadataEncoding) -> Result<Vec<u8>> {
393 StateMetadataDocument::from(self).pack(encoding)
394 }
395}
396
397#[cfg(feature = "iota-client")]
398mod client_document {
399 use identity_core::common::Timestamp;
400 use identity_did::DID;
401 use iota_interaction::rpc_types::IotaObjectData;
402
403 use crate::rebased::migration::unpack_identity_data;
404 use crate::rebased::migration::IdentityData;
405
406 use super::*;
407
408 impl IotaDocument {
409 pub fn unpack_from_iota_object_data(
422 did: &IotaDID,
423 data: &IotaObjectData,
424 allow_empty: bool,
425 ) -> Result<IotaDocument> {
426 let IdentityData {
427 multicontroller,
428 legacy_id,
429 created,
430 updated,
431 ..
432 } = unpack_identity_data(data.clone()).map_err(|_| {
433 Error::InvalidDoc(identity_document::Error::InvalidDocument(
434 "could not unpack identity data from IotaObjectData",
435 None,
436 ))
437 })?;
438 let did_network = did
439 .network_str()
440 .to_string()
441 .try_into()
442 .expect("did's network is a valid NetworkName");
443 let legacy_did = legacy_id.map(|id| IotaDID::new(&id.into_bytes(), &did_network));
444 let did_doc_bytes = multicontroller
445 .controlled_value()
446 .as_deref()
447 .ok_or_else(|| Error::DIDResolutionError("requested DID Document doesn't exist".to_string()))?;
448 let did_doc = Self::from_iota_document_data(did_doc_bytes, allow_empty, did, legacy_did, created, updated)?;
449
450 Ok(did_doc)
451 }
452
453 pub fn from_iota_document_data(
462 data: &[u8],
463 allow_empty: bool,
464 did: &IotaDID,
465 alternative_did: Option<IotaDID>,
466 created: Timestamp,
467 updated: Timestamp,
468 ) -> Result<Self> {
469 let mut did_doc = if data.is_empty() && allow_empty {
471 let mut empty_document = Self::new_with_id(did.clone());
473 empty_document.metadata.deactivated = Some(true);
474 empty_document
475 } else {
476 StateMetadataDocument::unpack(data).and_then(|state_metadata_doc| state_metadata_doc.into_iota_document(did))?
478 };
479
480 if let Some(alternative_did) = alternative_did {
482 did_doc.also_known_as_mut().prepend(alternative_did.into_url().into());
483 }
484
485 did_doc.metadata.created = Some(created);
487 did_doc.metadata.updated = Some(updated);
488
489 Ok(did_doc)
490 }
491 }
492}
493
494impl AsRef<CoreDocument> for IotaDocument {
495 fn as_ref(&self) -> &CoreDocument {
496 &self.document
497 }
498}
499
500#[cfg(feature = "revocation-bitmap")]
501mod iota_document_revocation {
502 use identity_credential::revocation::RevocationDocumentExt;
503 use identity_document::utils::DIDUrlQuery;
504
505 use crate::Error;
506 use crate::Result;
507
508 use super::IotaDocument;
509
510 impl IotaDocument {
511 pub fn revoke_credentials<'query, 'me, Q>(&mut self, service_query: Q, indices: &[u32]) -> Result<()>
514 where
515 Q: Into<DIDUrlQuery<'query>>,
516 {
517 self
518 .core_document_mut()
519 .revoke_credentials(service_query, indices)
520 .map_err(Error::RevocationError)
521 }
522
523 pub fn unrevoke_credentials<'query, 'me, Q>(&'me mut self, service_query: Q, indices: &[u32]) -> Result<()>
526 where
527 Q: Into<DIDUrlQuery<'query>>,
528 {
529 self
530 .core_document_mut()
531 .unrevoke_credentials(service_query, indices)
532 .map_err(Error::RevocationError)
533 }
534 }
535}
536
537impl From<IotaDocument> for CoreDocument {
538 fn from(document: IotaDocument) -> Self {
539 document.document
540 }
541}
542
543impl From<CoreDocument> for IotaDocument {
544 fn from(value: CoreDocument) -> Self {
545 IotaDocument {
546 document: value,
547 metadata: IotaDocumentMetadata::default(),
548 }
549 }
550}
551
552impl TryFrom<(CoreDocument, IotaDocumentMetadata)> for IotaDocument {
553 type Error = Error;
554 fn try_from(value: (CoreDocument, IotaDocumentMetadata)) -> std::result::Result<Self, Self::Error> {
561 ProvisionalIotaDocument {
562 document: value.0,
563 metadata: value.1,
564 }
565 .try_into()
566 }
567}
568
569impl Display for IotaDocument {
570 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
571 self.fmt_json(f)
572 }
573}
574
575#[cfg(test)]
576mod tests {
577 use identity_core::common::Timestamp;
578 use identity_core::convert::FromJson;
579 use identity_core::convert::ToJson;
580 use identity_did::DID;
581
582 use super::*;
583 use crate::test_utils::generate_method;
584
585 fn valid_did() -> IotaDID {
586 "did:iota:0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
587 .parse()
588 .unwrap()
589 }
590
591 fn generate_document(id: &IotaDID) -> IotaDocument {
592 let mut metadata: IotaDocumentMetadata = IotaDocumentMetadata::new();
593 metadata.created = Some(Timestamp::parse("2020-01-02T00:00:00Z").unwrap());
594 metadata.updated = Some(Timestamp::parse("2020-01-02T00:00:00Z").unwrap());
595
596 let document: CoreDocument = CoreDocument::builder(Object::default())
597 .id(id.clone().into())
598 .controller(id.clone().into())
599 .verification_method(generate_method(id, "#key-1"))
600 .verification_method(generate_method(id, "#key-2"))
601 .verification_method(generate_method(id, "#key-3"))
602 .authentication(generate_method(id, "#auth-key"))
603 .authentication(id.to_url().join("#key-3").unwrap())
604 .build()
605 .unwrap();
606
607 IotaDocument { document, metadata }
608 }
609
610 #[test]
611 fn test_new() {
612 let network: NetworkName = NetworkName::try_from("test").unwrap();
614 let placeholder: IotaDID = IotaDID::placeholder(&network);
615 let doc1: IotaDocument = IotaDocument::new(&network);
616 assert_eq!(doc1.id().network_str(), network.as_ref());
617 assert_eq!(doc1.id().tag_str(), placeholder.tag_str());
618 assert_eq!(doc1.id(), &placeholder);
619 assert_eq!(doc1.methods(None).len(), 0);
620 assert!(doc1.service().is_empty());
621
622 let did: IotaDID = valid_did();
624 let doc2: IotaDocument = IotaDocument::new_with_id(did.clone());
625 assert_eq!(doc2.id(), &did);
626 assert_eq!(doc2.methods(None).len(), 0);
627 assert!(doc2.service().is_empty());
628 }
629
630 #[test]
631 fn test_methods() {
632 let controller: IotaDID = valid_did();
633 let document: IotaDocument = generate_document(&controller);
634 let expected: [&'static str; 4] = ["key-1", "key-2", "key-3", "auth-key"];
635
636 let mut methods = document.methods(None).into_iter();
637 assert_eq!(methods.next().unwrap().id().fragment().unwrap(), expected[0]);
638 assert_eq!(methods.next().unwrap().id().fragment().unwrap(), expected[1]);
639 assert_eq!(methods.next().unwrap().id().fragment().unwrap(), expected[2]);
640 assert_eq!(methods.next().unwrap().id().fragment().unwrap(), expected[3]);
641 assert_eq!(methods.next(), None);
642 }
643
644 #[test]
645 fn test_services() {
646 let mut document: IotaDocument = IotaDocument::new_with_id(valid_did());
648 let url1: DIDUrl = document.id().to_url().join("#linked-domain").unwrap();
649 let service1: Service = Service::from_json(&format!(
650 r#"{{
651 "id":"{url1}",
652 "type": "LinkedDomains",
653 "serviceEndpoint": "https://bar.example.com"
654 }}"#
655 ))
656 .unwrap();
657 assert!(document.insert_service(service1.clone()).is_ok());
658 assert_eq!(1, document.service().len());
659 assert_eq!(document.resolve_service(&url1), Some(&service1));
660 assert_eq!(document.resolve_service("#linked-domain"), Some(&service1));
661 assert_eq!(document.resolve_service("linked-domain"), Some(&service1));
662 assert_eq!(document.resolve_service(""), None);
663 assert_eq!(document.resolve_service("#other"), None);
664
665 let url2: DIDUrl = document.id().to_url().join("#revocation").unwrap();
667 let service2: Service = Service::from_json(&format!(
668 r#"{{
669 "id":"{url2}",
670 "type": "RevocationBitmap2022",
671 "serviceEndpoint": "data:,blah"
672 }}"#
673 ))
674 .unwrap();
675 assert!(document.insert_service(service2.clone()).is_ok());
676 assert_eq!(2, document.service().len());
677 assert_eq!(document.resolve_service(&url2), Some(&service2));
678 assert_eq!(document.resolve_service("#revocation"), Some(&service2));
679 assert_eq!(document.resolve_service("revocation"), Some(&service2));
680 assert_eq!(document.resolve_service(""), None);
681 assert_eq!(document.resolve_service("#other"), None);
682
683 let duplicate: Service = Service::from_json(&format!(
685 r#"{{
686 "id":"{url1}",
687 "type": "DuplicateService",
688 "serviceEndpoint": "data:,duplicate"
689 }}"#
690 ))
691 .unwrap();
692 assert!(document.insert_service(duplicate.clone()).is_err());
693 assert_eq!(2, document.service().len());
694 let resolved: &Service = document.resolve_service(&url1).unwrap();
695 assert_eq!(resolved, &service1);
696 assert_ne!(resolved, &duplicate);
697
698 assert_eq!(service1, document.remove_service(&url1).unwrap());
700 assert_eq!(1, document.service().len());
701 let last_service: &Service = document.resolve_service(&url2).unwrap();
702 assert_eq!(last_service, &service2);
703
704 assert_eq!(service2, document.remove_service(&url2).unwrap());
705 assert_eq!(0, document.service().len());
706 }
707
708 #[test]
709 fn test_document_equality() {
710 let mut original_doc: IotaDocument = IotaDocument::new_with_id(valid_did());
711 let method1: VerificationMethod = generate_method(original_doc.id(), "test-0");
712 original_doc
713 .insert_method(method1, MethodScope::capability_invocation())
714 .unwrap();
715
716 let mut doc1 = original_doc.clone();
718 let method2: VerificationMethod = generate_method(original_doc.id(), "test-0");
719
720 doc1
721 .remove_method(&doc1.id().to_url().join("#test-0").unwrap())
722 .unwrap();
723 doc1
724 .insert_method(method2, MethodScope::capability_invocation())
725 .unwrap();
726
727 assert_ne!(original_doc, doc1);
730
731 let mut doc2 = doc1.clone();
732 let method3: VerificationMethod = generate_method(original_doc.id(), "test-0");
733
734 let insertion_result = doc2.insert_method(method3, MethodScope::capability_invocation());
735
736 assert!(insertion_result.is_err());
738 assert_eq!(doc1, doc2);
739 }
740
741 #[test]
742 fn test_unpack_empty() {
743 let did: IotaDID = "did:iota:0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
745 .parse()
746 .unwrap();
747 let document = IotaDocument::from_iota_document_data(
748 &[],
749 true,
750 &did,
751 None,
752 Timestamp::from_unix(12).unwrap(),
753 Timestamp::from_unix(34).unwrap(),
754 )
755 .unwrap();
756 assert_eq!(document.id(), &did);
757 assert_eq!(document.metadata.deactivated, Some(true));
758
759 let json: String = format!(
761 r#"{{"doc":{{"id":"{did}"}},"meta":{{"created":"1970-01-01T00:00:12Z","updated":"1970-01-01T00:00:34Z","deactivated":true}}}}"#
762 );
763 assert_eq!(document.to_json().unwrap(), json);
764
765 assert!(IotaDocument::from_iota_document_data(
767 &[],
768 false,
769 &did,
770 None,
771 Timestamp::from_unix(12).unwrap(),
772 Timestamp::from_unix(34).unwrap()
773 )
774 .is_err());
775
776 let packed: Vec<u8> = document.pack_with_encoding(StateMetadataEncoding::Json).unwrap();
778 let state_metadata_document: StateMetadataDocument = StateMetadataDocument::unpack(&packed).unwrap();
779 let unpacked_document: IotaDocument = state_metadata_document.into_iota_document(&did).unwrap();
780 assert!(unpacked_document.document.controller().is_none());
781 }
782
783 #[test]
784 fn test_json_roundtrip() {
785 let document: IotaDocument = generate_document(&valid_did());
786
787 let ser: String = document.to_json().unwrap();
788 let de: IotaDocument = IotaDocument::from_json(&ser).unwrap();
789 assert_eq!(document, de);
790 }
791
792 #[test]
793 fn test_json_fieldnames() {
794 let document: IotaDocument = IotaDocument::new_with_id(valid_did());
796 let serialization: String = document.to_json().unwrap();
797 assert_eq!(
798 serialization,
799 format!("{{\"doc\":{},\"meta\":{}}}", document.document, document.metadata)
800 );
801 }
802
803 #[test]
804 fn deserializing_id_from_other_method_fails() {
805 const JSON_DOC_INVALID_ID: &str = r#"
806 {
807 "doc": {
808 "id": "did:foo:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
809 "verificationMethod": [
810 {
811 "id": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa#issuerKey",
812 "controller": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
813 "type": "Ed25519VerificationKey2018",
814 "publicKeyMultibase": "zFVen3X669xLzsi6N2V91DoiyzHzg1uAgqiT8jZ9nS96Z"
815 }
816 ]
817 },
818 "meta": {
819 "created": "2022-08-31T09:33:31Z",
820 "updated": "2022-08-31T09:33:31Z"
821 }
822 }"#;
823
824 let deserialization_result = IotaDocument::from_json(&JSON_DOC_INVALID_ID);
825
826 assert!(deserialization_result.is_err());
827
828 const JSON_DOC_CORRECT_ID: &str = r#"
830 {
831 "doc": {
832 "id": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
833 "verificationMethod": [
834 {
835 "id": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa#issuerKey",
836 "controller": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
837 "type": "Ed25519VerificationKey2018",
838 "publicKeyMultibase": "zFVen3X669xLzsi6N2V91DoiyzHzg1uAgqiT8jZ9nS96Z"
839 }
840 ]
841 },
842 "meta": {
843 "created": "2022-08-31T09:33:31Z",
844 "updated": "2022-08-31T09:33:31Z"
845 }
846 }"#;
847
848 let corrected_deserialization_result = IotaDocument::from_json(&JSON_DOC_CORRECT_ID);
849 assert!(corrected_deserialization_result.is_ok());
850 }
851
852 #[test]
853 fn deserializing_controller_from_other_method_fails() {
854 const JSON_DOC_INVALID_CONTROLLER_ID: &str = r#"
855 {
856 "doc": {
857 "id": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38",
858 "controller": "did:example:rms:0xfbaaa919b51112d51a8f18b1500d98f0b2e91d793bc5b27fd5ab04cb1b806343",
859 "verificationMethod": [
860 {
861 "id": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38#key-2",
862 "controller": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38",
863 "type": "Ed25519VerificationKey2018",
864 "publicKeyMultibase": "z7eTUXFdLCFg1LFVFhG8qUAM2aSjfTuPLB2x9XGXgQh6G"
865 }
866 ]
867 },
868 "meta": {
869 "created": "2023-01-25T15:48:09Z",
870 "updated": "2023-01-25T15:48:09Z"
871 }
872 }
873 "#;
874
875 let deserialization_result = IotaDocument::from_json(&JSON_DOC_INVALID_CONTROLLER_ID);
876 assert!(deserialization_result.is_err());
877
878 const JSON_DOC_CORRECT_CONTROLLER_ID: &str = r#"
880 {
881 "doc": {
882 "id": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38",
883 "controller": "did:iota:rms:0xfbaaa919b51112d51a8f18b1500d98f0b2e91d793bc5b27fd5ab04cb1b806343",
884 "verificationMethod": [
885 {
886 "id": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38#key-2",
887 "controller": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38",
888 "type": "Ed25519VerificationKey2018",
889 "publicKeyMultibase": "z7eTUXFdLCFg1LFVFhG8qUAM2aSjfTuPLB2x9XGXgQh6G"
890 }
891 ]
892 },
893 "meta": {
894 "created": "2023-01-25T15:48:09Z",
895 "updated": "2023-01-25T15:48:09Z"
896 }
897}
898"#;
899 let corrected_deserialization_result = IotaDocument::from_json(JSON_DOC_CORRECT_CONTROLLER_ID);
900 assert!(corrected_deserialization_result.is_ok());
901 }
902
903 #[test]
904 fn controller_iterator_without_controller() {
905 const DOC_JSON: &str = r#"
906 {
907 "doc": {
908 "id": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
909 },
910 "meta": {
911 "created": "2022-08-31T09:33:31Z",
912 "updated": "2022-08-31T09:33:31Z"
913 }
914 }
915 "#;
916
917 let doc = IotaDocument::from_json(DOC_JSON).unwrap();
918 assert!(doc.controller().next().is_none());
919 }
920
921 #[test]
922 fn controller_iterator_with_controller() {
923 const DOC_JSON: &str = r#"
924 {
925 "doc": {
926 "id": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38",
927 "controller": "did:iota:rms:0xfbaaa919b51112d51a8f18b1500d98f0b2e91d793bc5b27fd5ab04cb1b806343"
928 },
929 "meta": {
930 "created": "2023-01-25T15:48:09Z",
931 "updated": "2023-01-25T15:48:09Z"
932 }
933 }
934 "#;
935 let doc = IotaDocument::from_json(DOC_JSON).unwrap();
936 let expected_controller =
937 IotaDID::parse("did:iota:rms:0xfbaaa919b51112d51a8f18b1500d98f0b2e91d793bc5b27fd5ab04cb1b806343").unwrap();
938 let controllers: Vec<&IotaDID> = doc.controller().collect();
939 assert_eq!(&controllers, &[&expected_controller]);
940 }
941
942 #[test]
943 fn try_from_doc_metadata() {
944 const DOC_JSON_NOT_IOTA_DOCUMENT_BECAUSE_OF_ID: &str = r#"
945 {
946 "id": "did:foo:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
947 "verificationMethod": [
948 {
949 "id": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa#issuerKey",
950 "controller": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
951 "type": "Ed25519VerificationKey2018",
952 "publicKeyMultibase": "zFVen3X669xLzsi6N2V91DoiyzHzg1uAgqiT8jZ9nS96Z"
953 }
954 ]
955 }
956 "#;
957
958 const DOC_JSON_NOT_IOTA_DOCUMENT_BECAUSE_OF_CONTROLLER: &str = r#"
959 {
960 "id": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38",
961 "controller": "did:example:rms:0xfbaaa919b51112d51a8f18b1500d98f0b2e91d793bc5b27fd5ab04cb1b806343",
962 "verificationMethod": [
963 {
964 "id": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38#key-2",
965 "controller": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38",
966 "type": "Ed25519VerificationKey2018",
967 "publicKeyMultibase": "z7eTUXFdLCFg1LFVFhG8qUAM2aSjfTuPLB2x9XGXgQh6G"
968 }
969 ]
970 }
971 "#;
972
973 const METADATA_JSON: &str = r#"
974 {
975 "created": "2022-08-31T09:33:31Z",
976 "updated": "2022-08-31T09:33:31Z"
977 }
978 "#;
979
980 const DOCUMENT_WITH_IOTA_ID_AND_CONTROLLER_JSON: &str = r#"
981 {
982 "id": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38",
983 "controller": "did:iota:rms:0xfbaaa919b51112d51a8f18b1500d98f0b2e91d793bc5b27fd5ab04cb1b806343",
984 "verificationMethod": [
985 {
986 "id": "did:foo:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa#issuerKey",
987 "controller": "did:bar:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
988 "type": "Ed25519VerificationKey2018",
989 "publicKeyMultibase": "zFVen3X669xLzsi6N2V91DoiyzHzg1uAgqiT8jZ9nS96Z"
990 }
991 ]
992 }
993 "#;
994
995 let doc_not_iota_because_of_id: CoreDocument =
996 CoreDocument::from_json(DOC_JSON_NOT_IOTA_DOCUMENT_BECAUSE_OF_ID).unwrap();
997 let doc_not_iota_because_of_controller: CoreDocument =
998 CoreDocument::from_json(DOC_JSON_NOT_IOTA_DOCUMENT_BECAUSE_OF_CONTROLLER).unwrap();
999 let doc_with_iota_id_and_controller: CoreDocument =
1000 CoreDocument::from_json(DOCUMENT_WITH_IOTA_ID_AND_CONTROLLER_JSON).unwrap();
1001 let metadata: IotaDocumentMetadata = IotaDocumentMetadata::from_json(METADATA_JSON).unwrap();
1002
1003 assert!(IotaDocument::try_from((doc_not_iota_because_of_id, metadata.clone())).is_err());
1004
1005 assert!(IotaDocument::try_from((doc_not_iota_because_of_controller, metadata.clone())).is_err());
1006
1007 assert!(IotaDocument::try_from((doc_with_iota_id_and_controller, metadata)).is_ok());
1008 }
1009}