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 unpacked = unpack_identity_data(did, data).map_err(|_| {
427 Error::InvalidDoc(identity_document::Error::InvalidDocument(
428 "could not unpack identity data from IotaObjectData",
429 None,
430 ))
431 })?;
432 let IdentityData {
433 multicontroller,
434 legacy_id,
435 created,
436 updated,
437 ..
438 } = match unpacked {
439 Some(data) => data,
440 None => {
441 return Err(Error::InvalidDoc(identity_document::Error::InvalidDocument(
442 "given IotaObjectData did not contain a document",
443 None,
444 )));
445 }
446 };
447 let did_network = did
448 .network_str()
449 .to_string()
450 .try_into()
451 .expect("did's network is a valid NetworkName");
452 let legacy_did = legacy_id.map(|id| IotaDID::new(&id.into_bytes(), &did_network));
453 let did_doc_bytes = multicontroller
454 .controlled_value()
455 .as_deref()
456 .ok_or_else(|| Error::DIDResolutionError("requested DID Document doesn't exist".to_string()))?;
457 let did_doc = Self::from_iota_document_data(did_doc_bytes, allow_empty, did, legacy_did, created, updated)?;
458
459 Ok(did_doc)
460 }
461
462 pub fn from_iota_document_data(
471 data: &[u8],
472 allow_empty: bool,
473 did: &IotaDID,
474 alternative_did: Option<IotaDID>,
475 created: Timestamp,
476 updated: Timestamp,
477 ) -> Result<Self> {
478 let mut did_doc = if data.is_empty() && allow_empty {
480 let mut empty_document = Self::new_with_id(did.clone());
482 empty_document.metadata.deactivated = Some(true);
483 empty_document
484 } else {
485 StateMetadataDocument::unpack(data).and_then(|state_metadata_doc| state_metadata_doc.into_iota_document(did))?
487 };
488
489 if let Some(alternative_did) = alternative_did {
491 did_doc.also_known_as_mut().prepend(alternative_did.into_url().into());
492 }
493
494 did_doc.metadata.created = Some(created);
496 did_doc.metadata.updated = Some(updated);
497
498 Ok(did_doc)
499 }
500 }
501}
502
503impl AsRef<CoreDocument> for IotaDocument {
504 fn as_ref(&self) -> &CoreDocument {
505 &self.document
506 }
507}
508
509#[cfg(feature = "revocation-bitmap")]
510mod iota_document_revocation {
511 use identity_credential::revocation::RevocationDocumentExt;
512 use identity_document::utils::DIDUrlQuery;
513
514 use crate::Error;
515 use crate::Result;
516
517 use super::IotaDocument;
518
519 impl IotaDocument {
520 pub fn revoke_credentials<'query, 'me, Q>(&mut self, service_query: Q, indices: &[u32]) -> Result<()>
523 where
524 Q: Into<DIDUrlQuery<'query>>,
525 {
526 self
527 .core_document_mut()
528 .revoke_credentials(service_query, indices)
529 .map_err(Error::RevocationError)
530 }
531
532 pub fn unrevoke_credentials<'query, 'me, Q>(&'me mut self, service_query: Q, indices: &[u32]) -> Result<()>
535 where
536 Q: Into<DIDUrlQuery<'query>>,
537 {
538 self
539 .core_document_mut()
540 .unrevoke_credentials(service_query, indices)
541 .map_err(Error::RevocationError)
542 }
543 }
544}
545
546impl From<IotaDocument> for CoreDocument {
547 fn from(document: IotaDocument) -> Self {
548 document.document
549 }
550}
551
552impl From<CoreDocument> for IotaDocument {
553 fn from(value: CoreDocument) -> Self {
554 IotaDocument {
555 document: value,
556 metadata: IotaDocumentMetadata::default(),
557 }
558 }
559}
560
561impl TryFrom<(CoreDocument, IotaDocumentMetadata)> for IotaDocument {
562 type Error = Error;
563 fn try_from(value: (CoreDocument, IotaDocumentMetadata)) -> std::result::Result<Self, Self::Error> {
570 ProvisionalIotaDocument {
571 document: value.0,
572 metadata: value.1,
573 }
574 .try_into()
575 }
576}
577
578impl Display for IotaDocument {
579 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
580 self.fmt_json(f)
581 }
582}
583
584#[cfg(test)]
585mod tests {
586 use identity_core::common::Timestamp;
587 use identity_core::convert::FromJson;
588 use identity_core::convert::ToJson;
589 use identity_did::DID;
590
591 use super::*;
592 use crate::test_utils::generate_method;
593
594 fn valid_did() -> IotaDID {
595 "did:iota:0xAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
596 .parse()
597 .unwrap()
598 }
599
600 fn generate_document(id: &IotaDID) -> IotaDocument {
601 let mut metadata: IotaDocumentMetadata = IotaDocumentMetadata::new();
602 metadata.created = Some(Timestamp::parse("2020-01-02T00:00:00Z").unwrap());
603 metadata.updated = Some(Timestamp::parse("2020-01-02T00:00:00Z").unwrap());
604
605 let document: CoreDocument = CoreDocument::builder(Object::default())
606 .id(id.clone().into())
607 .controller(id.clone().into())
608 .verification_method(generate_method(id, "#key-1"))
609 .verification_method(generate_method(id, "#key-2"))
610 .verification_method(generate_method(id, "#key-3"))
611 .authentication(generate_method(id, "#auth-key"))
612 .authentication(id.to_url().join("#key-3").unwrap())
613 .build()
614 .unwrap();
615
616 IotaDocument { document, metadata }
617 }
618
619 #[test]
620 fn test_new() {
621 let network: NetworkName = NetworkName::try_from("test").unwrap();
623 let placeholder: IotaDID = IotaDID::placeholder(&network);
624 let doc1: IotaDocument = IotaDocument::new(&network);
625 assert_eq!(doc1.id().network_str(), network.as_ref());
626 assert_eq!(doc1.id().tag_str(), placeholder.tag_str());
627 assert_eq!(doc1.id(), &placeholder);
628 assert_eq!(doc1.methods(None).len(), 0);
629 assert!(doc1.service().is_empty());
630
631 let did: IotaDID = valid_did();
633 let doc2: IotaDocument = IotaDocument::new_with_id(did.clone());
634 assert_eq!(doc2.id(), &did);
635 assert_eq!(doc2.methods(None).len(), 0);
636 assert!(doc2.service().is_empty());
637 }
638
639 #[test]
640 fn test_methods() {
641 let controller: IotaDID = valid_did();
642 let document: IotaDocument = generate_document(&controller);
643 let expected: [&'static str; 4] = ["key-1", "key-2", "key-3", "auth-key"];
644
645 let mut methods = document.methods(None).into_iter();
646 assert_eq!(methods.next().unwrap().id().fragment().unwrap(), expected[0]);
647 assert_eq!(methods.next().unwrap().id().fragment().unwrap(), expected[1]);
648 assert_eq!(methods.next().unwrap().id().fragment().unwrap(), expected[2]);
649 assert_eq!(methods.next().unwrap().id().fragment().unwrap(), expected[3]);
650 assert_eq!(methods.next(), None);
651 }
652
653 #[test]
654 fn test_services() {
655 let mut document: IotaDocument = IotaDocument::new_with_id(valid_did());
657 let url1: DIDUrl = document.id().to_url().join("#linked-domain").unwrap();
658 let service1: Service = Service::from_json(&format!(
659 r#"{{
660 "id":"{url1}",
661 "type": "LinkedDomains",
662 "serviceEndpoint": "https://bar.example.com"
663 }}"#
664 ))
665 .unwrap();
666 assert!(document.insert_service(service1.clone()).is_ok());
667 assert_eq!(1, document.service().len());
668 assert_eq!(document.resolve_service(&url1), Some(&service1));
669 assert_eq!(document.resolve_service("#linked-domain"), Some(&service1));
670 assert_eq!(document.resolve_service("linked-domain"), Some(&service1));
671 assert_eq!(document.resolve_service(""), None);
672 assert_eq!(document.resolve_service("#other"), None);
673
674 let url2: DIDUrl = document.id().to_url().join("#revocation").unwrap();
676 let service2: Service = Service::from_json(&format!(
677 r#"{{
678 "id":"{url2}",
679 "type": "RevocationBitmap2022",
680 "serviceEndpoint": "data:,blah"
681 }}"#
682 ))
683 .unwrap();
684 assert!(document.insert_service(service2.clone()).is_ok());
685 assert_eq!(2, document.service().len());
686 assert_eq!(document.resolve_service(&url2), Some(&service2));
687 assert_eq!(document.resolve_service("#revocation"), Some(&service2));
688 assert_eq!(document.resolve_service("revocation"), Some(&service2));
689 assert_eq!(document.resolve_service(""), None);
690 assert_eq!(document.resolve_service("#other"), None);
691
692 let duplicate: Service = Service::from_json(&format!(
694 r#"{{
695 "id":"{url1}",
696 "type": "DuplicateService",
697 "serviceEndpoint": "data:,duplicate"
698 }}"#
699 ))
700 .unwrap();
701 assert!(document.insert_service(duplicate.clone()).is_err());
702 assert_eq!(2, document.service().len());
703 let resolved: &Service = document.resolve_service(&url1).unwrap();
704 assert_eq!(resolved, &service1);
705 assert_ne!(resolved, &duplicate);
706
707 assert_eq!(service1, document.remove_service(&url1).unwrap());
709 assert_eq!(1, document.service().len());
710 let last_service: &Service = document.resolve_service(&url2).unwrap();
711 assert_eq!(last_service, &service2);
712
713 assert_eq!(service2, document.remove_service(&url2).unwrap());
714 assert_eq!(0, document.service().len());
715 }
716
717 #[test]
718 fn test_document_equality() {
719 let mut original_doc: IotaDocument = IotaDocument::new_with_id(valid_did());
720 let method1: VerificationMethod = generate_method(original_doc.id(), "test-0");
721 original_doc
722 .insert_method(method1, MethodScope::capability_invocation())
723 .unwrap();
724
725 let mut doc1 = original_doc.clone();
727 let method2: VerificationMethod = generate_method(original_doc.id(), "test-0");
728
729 doc1
730 .remove_method(&doc1.id().to_url().join("#test-0").unwrap())
731 .unwrap();
732 doc1
733 .insert_method(method2, MethodScope::capability_invocation())
734 .unwrap();
735
736 assert_ne!(original_doc, doc1);
739
740 let mut doc2 = doc1.clone();
741 let method3: VerificationMethod = generate_method(original_doc.id(), "test-0");
742
743 let insertion_result = doc2.insert_method(method3, MethodScope::capability_invocation());
744
745 assert!(insertion_result.is_err());
747 assert_eq!(doc1, doc2);
748 }
749
750 #[test]
751 fn test_unpack_empty() {
752 let did: IotaDID = "did:iota:0xBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
754 .parse()
755 .unwrap();
756 let document = IotaDocument::from_iota_document_data(
757 &[],
758 true,
759 &did,
760 None,
761 Timestamp::from_unix(12).unwrap(),
762 Timestamp::from_unix(34).unwrap(),
763 )
764 .unwrap();
765 assert_eq!(document.id(), &did);
766 assert_eq!(document.metadata.deactivated, Some(true));
767
768 let json: String = format!(
770 r#"{{"doc":{{"id":"{did}"}},"meta":{{"created":"1970-01-01T00:00:12Z","updated":"1970-01-01T00:00:34Z","deactivated":true}}}}"#
771 );
772 assert_eq!(document.to_json().unwrap(), json);
773
774 assert!(IotaDocument::from_iota_document_data(
776 &[],
777 false,
778 &did,
779 None,
780 Timestamp::from_unix(12).unwrap(),
781 Timestamp::from_unix(34).unwrap()
782 )
783 .is_err());
784
785 let packed: Vec<u8> = document.pack_with_encoding(StateMetadataEncoding::Json).unwrap();
787 let state_metadata_document: StateMetadataDocument = StateMetadataDocument::unpack(&packed).unwrap();
788 let unpacked_document: IotaDocument = state_metadata_document.into_iota_document(&did).unwrap();
789 assert!(unpacked_document.document.controller().is_none());
790 }
791
792 #[test]
793 fn test_json_roundtrip() {
794 let document: IotaDocument = generate_document(&valid_did());
795
796 let ser: String = document.to_json().unwrap();
797 let de: IotaDocument = IotaDocument::from_json(&ser).unwrap();
798 assert_eq!(document, de);
799 }
800
801 #[test]
802 fn test_json_fieldnames() {
803 let document: IotaDocument = IotaDocument::new_with_id(valid_did());
805 let serialization: String = document.to_json().unwrap();
806 assert_eq!(
807 serialization,
808 format!("{{\"doc\":{},\"meta\":{}}}", document.document, document.metadata)
809 );
810 }
811
812 #[test]
813 fn deserializing_id_from_other_method_fails() {
814 const JSON_DOC_INVALID_ID: &str = r#"
815 {
816 "doc": {
817 "id": "did:foo:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
818 "verificationMethod": [
819 {
820 "id": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa#issuerKey",
821 "controller": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
822 "type": "Ed25519VerificationKey2018",
823 "publicKeyMultibase": "zFVen3X669xLzsi6N2V91DoiyzHzg1uAgqiT8jZ9nS96Z"
824 }
825 ]
826 },
827 "meta": {
828 "created": "2022-08-31T09:33:31Z",
829 "updated": "2022-08-31T09:33:31Z"
830 }
831 }"#;
832
833 let deserialization_result = IotaDocument::from_json(&JSON_DOC_INVALID_ID);
834
835 assert!(deserialization_result.is_err());
836
837 const JSON_DOC_CORRECT_ID: &str = r#"
839 {
840 "doc": {
841 "id": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
842 "verificationMethod": [
843 {
844 "id": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa#issuerKey",
845 "controller": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
846 "type": "Ed25519VerificationKey2018",
847 "publicKeyMultibase": "zFVen3X669xLzsi6N2V91DoiyzHzg1uAgqiT8jZ9nS96Z"
848 }
849 ]
850 },
851 "meta": {
852 "created": "2022-08-31T09:33:31Z",
853 "updated": "2022-08-31T09:33:31Z"
854 }
855 }"#;
856
857 let corrected_deserialization_result = IotaDocument::from_json(&JSON_DOC_CORRECT_ID);
858 assert!(corrected_deserialization_result.is_ok());
859 }
860
861 #[test]
862 fn deserializing_controller_from_other_method_fails() {
863 const JSON_DOC_INVALID_CONTROLLER_ID: &str = r#"
864 {
865 "doc": {
866 "id": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38",
867 "controller": "did:example:rms:0xfbaaa919b51112d51a8f18b1500d98f0b2e91d793bc5b27fd5ab04cb1b806343",
868 "verificationMethod": [
869 {
870 "id": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38#key-2",
871 "controller": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38",
872 "type": "Ed25519VerificationKey2018",
873 "publicKeyMultibase": "z7eTUXFdLCFg1LFVFhG8qUAM2aSjfTuPLB2x9XGXgQh6G"
874 }
875 ]
876 },
877 "meta": {
878 "created": "2023-01-25T15:48:09Z",
879 "updated": "2023-01-25T15:48:09Z"
880 }
881 }
882 "#;
883
884 let deserialization_result = IotaDocument::from_json(&JSON_DOC_INVALID_CONTROLLER_ID);
885 assert!(deserialization_result.is_err());
886
887 const JSON_DOC_CORRECT_CONTROLLER_ID: &str = r#"
889 {
890 "doc": {
891 "id": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38",
892 "controller": "did:iota:rms:0xfbaaa919b51112d51a8f18b1500d98f0b2e91d793bc5b27fd5ab04cb1b806343",
893 "verificationMethod": [
894 {
895 "id": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38#key-2",
896 "controller": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38",
897 "type": "Ed25519VerificationKey2018",
898 "publicKeyMultibase": "z7eTUXFdLCFg1LFVFhG8qUAM2aSjfTuPLB2x9XGXgQh6G"
899 }
900 ]
901 },
902 "meta": {
903 "created": "2023-01-25T15:48:09Z",
904 "updated": "2023-01-25T15:48:09Z"
905 }
906}
907"#;
908 let corrected_deserialization_result = IotaDocument::from_json(JSON_DOC_CORRECT_CONTROLLER_ID);
909 assert!(corrected_deserialization_result.is_ok());
910 }
911
912 #[test]
913 fn controller_iterator_without_controller() {
914 const DOC_JSON: &str = r#"
915 {
916 "doc": {
917 "id": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
918 },
919 "meta": {
920 "created": "2022-08-31T09:33:31Z",
921 "updated": "2022-08-31T09:33:31Z"
922 }
923 }
924 "#;
925
926 let doc = IotaDocument::from_json(DOC_JSON).unwrap();
927 assert!(doc.controller().next().is_none());
928 }
929
930 #[test]
931 fn controller_iterator_with_controller() {
932 const DOC_JSON: &str = r#"
933 {
934 "doc": {
935 "id": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38",
936 "controller": "did:iota:rms:0xfbaaa919b51112d51a8f18b1500d98f0b2e91d793bc5b27fd5ab04cb1b806343"
937 },
938 "meta": {
939 "created": "2023-01-25T15:48:09Z",
940 "updated": "2023-01-25T15:48:09Z"
941 }
942 }
943 "#;
944 let doc = IotaDocument::from_json(DOC_JSON).unwrap();
945 let expected_controller =
946 IotaDID::parse("did:iota:rms:0xfbaaa919b51112d51a8f18b1500d98f0b2e91d793bc5b27fd5ab04cb1b806343").unwrap();
947 let controllers: Vec<&IotaDID> = doc.controller().collect();
948 assert_eq!(&controllers, &[&expected_controller]);
949 }
950
951 #[test]
952 fn try_from_doc_metadata() {
953 const DOC_JSON_NOT_IOTA_DOCUMENT_BECAUSE_OF_ID: &str = r#"
954 {
955 "id": "did:foo:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
956 "verificationMethod": [
957 {
958 "id": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa#issuerKey",
959 "controller": "did:iota:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
960 "type": "Ed25519VerificationKey2018",
961 "publicKeyMultibase": "zFVen3X669xLzsi6N2V91DoiyzHzg1uAgqiT8jZ9nS96Z"
962 }
963 ]
964 }
965 "#;
966
967 const DOC_JSON_NOT_IOTA_DOCUMENT_BECAUSE_OF_CONTROLLER: &str = r#"
968 {
969 "id": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38",
970 "controller": "did:example:rms:0xfbaaa919b51112d51a8f18b1500d98f0b2e91d793bc5b27fd5ab04cb1b806343",
971 "verificationMethod": [
972 {
973 "id": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38#key-2",
974 "controller": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38",
975 "type": "Ed25519VerificationKey2018",
976 "publicKeyMultibase": "z7eTUXFdLCFg1LFVFhG8qUAM2aSjfTuPLB2x9XGXgQh6G"
977 }
978 ]
979 }
980 "#;
981
982 const METADATA_JSON: &str = r#"
983 {
984 "created": "2022-08-31T09:33:31Z",
985 "updated": "2022-08-31T09:33:31Z"
986 }
987 "#;
988
989 const DOCUMENT_WITH_IOTA_ID_AND_CONTROLLER_JSON: &str = r#"
990 {
991 "id": "did:iota:rms:0x7591a0bc872e3a4ab66228d65773961a7a95d2299ec8464331c80fcd86b35f38",
992 "controller": "did:iota:rms:0xfbaaa919b51112d51a8f18b1500d98f0b2e91d793bc5b27fd5ab04cb1b806343",
993 "verificationMethod": [
994 {
995 "id": "did:foo:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa#issuerKey",
996 "controller": "did:bar:0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
997 "type": "Ed25519VerificationKey2018",
998 "publicKeyMultibase": "zFVen3X669xLzsi6N2V91DoiyzHzg1uAgqiT8jZ9nS96Z"
999 }
1000 ]
1001 }
1002 "#;
1003
1004 let doc_not_iota_because_of_id: CoreDocument =
1005 CoreDocument::from_json(DOC_JSON_NOT_IOTA_DOCUMENT_BECAUSE_OF_ID).unwrap();
1006 let doc_not_iota_because_of_controller: CoreDocument =
1007 CoreDocument::from_json(DOC_JSON_NOT_IOTA_DOCUMENT_BECAUSE_OF_CONTROLLER).unwrap();
1008 let doc_with_iota_id_and_controller: CoreDocument =
1009 CoreDocument::from_json(DOCUMENT_WITH_IOTA_ID_AND_CONTROLLER_JSON).unwrap();
1010 let metadata: IotaDocumentMetadata = IotaDocumentMetadata::from_json(METADATA_JSON).unwrap();
1011
1012 assert!(IotaDocument::try_from((doc_not_iota_because_of_id, metadata.clone())).is_err());
1013
1014 assert!(IotaDocument::try_from((doc_not_iota_because_of_controller, metadata.clone())).is_err());
1015
1016 assert!(IotaDocument::try_from((doc_with_iota_id_and_controller, metadata)).is_ok());
1017 }
1018}