trustchain_core/
resolver.rs

1//! DID resolution and `DIDResolver` implementation.
2use crate::TRUSTCHAIN_PROOF_SERVICE_ID_VALUE;
3use async_trait::async_trait;
4use did_method_key::DIDKey;
5use serde_json::Value;
6use ssi::did::{Document, Service, ServiceEndpoint};
7use ssi::did_resolve::{
8    DIDResolver, DocumentMetadata, Metadata, ResolutionInputMetadata, ResolutionMetadata,
9};
10use ssi::one_or_many::OneOrMany;
11use std::collections::HashMap;
12use thiserror::Error;
13
14/// An error relating to Trustchain resolution.
15#[derive(Error, Debug)]
16pub enum ResolverError {
17    /// Controller is already present in DID document.
18    #[error("Controller is already present in DID document.")]
19    ControllerAlreadyPresent,
20    /// Failed to convert to Trustchain document and metadata.
21    #[error("Failed to convert to Trustchain document and metadata: {0}")]
22    FailedToConvertToTrustchain(String),
23    /// Multiple Trustchain proof service entries are present.
24    #[error("Multiple Trustchain proof service entries are present.")]
25    MultipleTrustchainProofService,
26    /// No Trustchain proof service is present.
27    #[error("No Trustchain proof service is present.")]
28    NoTrustchainProofService,
29    /// Cannot connect to sidetree server.
30    #[error("Cannot connect to sidetree server.")]
31    ConnectionFailure,
32    /// DID does not exist.
33    #[error("DID: {0} does not exist.")]
34    NonExistentDID(String),
35    /// DID is not found.
36    #[error("DID: {0} is not found.")]
37    DIDNotFound(String),
38    /// General resolver error with resolution metadata.
39    #[error("Resolver error with resolution metadata.")]
40    FailureWithMetadata(ResolutionMetadata),
41}
42
43/// Type for resolver result.
44pub type ResolverResult = Result<
45    (
46        ResolutionMetadata,
47        Option<Document>,
48        Option<DocumentMetadata>,
49    ),
50    ResolverError,
51>;
52
53/// Adds the controller property to a resolved DID document, using the
54/// value passed in the controller_did argument. This must be the DID of
55/// the subject (id property) found in the upstream DID document.
56fn add_controller(mut doc: Document, controller_did: &str) -> Result<Document, ResolverError> {
57    // Check controller is empty and if not throw error.
58    if doc.controller.is_some() {
59        return Err(ResolverError::ControllerAlreadyPresent);
60    }
61
62    // Add the controller property to the DID document.
63    doc.controller = Some(OneOrMany::One(controller_did.to_string()));
64
65    // Return updated DID document.
66    Ok(doc)
67}
68
69/// Gets a result of an index of a single Trustchain proof service, otherwise relevant error.
70fn get_proof_idx(doc: &Document) -> Result<usize, ResolverError> {
71    let mut idxs: Vec<usize> = Vec::new();
72    let fragment = TRUSTCHAIN_PROOF_SERVICE_ID_VALUE;
73    for (idx, service) in doc.service.iter().flatten().enumerate() {
74        if let [service_fragment, _] = service.id.rsplitn(2, '#').collect::<Vec<&str>>().as_slice()
75        {
76            if service_fragment == &fragment {
77                idxs.push(idx);
78            }
79        }
80    }
81    match idxs.len() {
82        0 => Err(ResolverError::NoTrustchainProofService),
83        1 => Ok(idxs[0]),
84        _ => Err(ResolverError::MultipleTrustchainProofService),
85    }
86}
87
88/// Gets a result of a reference to a single Trustchain proof service, otherwise relevant error.
89fn get_proof_service(doc: &Document) -> Result<&Service, ResolverError> {
90    // Extract proof service as an owned service
91    let idxs = get_proof_idx(doc);
92    match idxs {
93        Ok(idx) => Ok(&doc.service.as_ref().unwrap()[idx]),
94        Err(e) => Err(e),
95    }
96}
97
98/// Gets the value of a key in a Trustchain proof service.
99fn get_from_proof_service<'a>(proof_service: &'a Service, key: &str) -> Option<&'a String> {
100    // Destructure nested enums and extract controller from a proof service
101    let value: Option<&String> = match proof_service.service_endpoint.as_ref() {
102        Some(OneOrMany::One(ServiceEndpoint::Map(Value::Object(v)))) => match &v[key] {
103            Value::String(s) => Some(s),
104            _ => None,
105        },
106        _ => None,
107    };
108    value
109}
110
111/// Adds a proof from a DID Document to DocumentMetadata.
112fn add_proof(doc: &Document, mut doc_meta: DocumentMetadata) -> DocumentMetadata {
113    // Get proof service
114    let proof_service = get_proof_service(doc);
115
116    // Handle result
117    if let Ok(proof_service) = proof_service {
118        // Get proof value and controller (uDID)
119        let proof_value = get_from_proof_service(proof_service, "proofValue");
120        let controller = get_from_proof_service(proof_service, "controller");
121        // If not None, add to new HashMap
122        if let (Some(property_set), Some(proof_value), Some(controller)) =
123            (doc_meta.property_set.as_mut(), proof_value, controller)
124        {
125            // Make new HashMap; add keys and values
126            let mut proof_hash_map: HashMap<String, Metadata> = HashMap::new();
127            proof_hash_map.insert(String::from("id"), Metadata::String(controller.to_owned()));
128            proof_hash_map.insert(
129                String::from("type"),
130                Metadata::String("JsonWebSignature2020".to_string()),
131            );
132            proof_hash_map.insert(
133                String::from("proofValue"),
134                Metadata::String(proof_value.to_owned()),
135            );
136
137            // Insert new HashMap of Metadata::Map()
138            property_set.insert(String::from("proof"), Metadata::Map(proof_hash_map));
139            return doc_meta;
140        }
141    }
142    // If there are zero or multiple proof services, do nothing
143    doc_meta
144}
145
146/// Removes Trustchain proof service from passed document if it exists.
147fn remove_proof_service(mut doc: Document) -> Document {
148    if doc.service.is_some() {
149        let idx_result = get_proof_idx(&doc);
150        if let Ok(idx) = idx_result {
151            let services = doc.service.as_mut().unwrap();
152            services.remove(idx);
153            if services.is_empty() {
154                doc.service = None;
155            }
156        }
157    }
158    doc
159}
160
161/// Converts a DID Document from a resolved DID to the Trustchain resolved format.
162fn transform_doc(doc: &Document, controller_did: &str) -> Document {
163    // Clone the passed DID document.
164    let doc_clone = doc.clone();
165
166    // Add controller
167    let doc_clone =
168        add_controller(doc_clone, controller_did).expect("Controller already present in document.");
169
170    // Remove the proof service from the document.
171    remove_proof_service(doc_clone)
172}
173
174/// Converts DID Document Metadata from a resolved DID to the Trustchain resolved format.
175fn transform_doc_metadata(doc: &Document, doc_meta: DocumentMetadata) -> DocumentMetadata {
176    // Add proof property to the DID Document Metadata (if it exists).
177    add_proof(doc, doc_meta)
178}
179
180/// Converts DID Document + Metadata to the Trustchain resolved format.
181fn transform_as_result(
182    sidetree_res_meta: ResolutionMetadata,
183    sidetree_doc: Document,
184    sidetree_doc_meta: DocumentMetadata,
185) -> Result<(ResolutionMetadata, Document, DocumentMetadata), ResolverError> {
186    // Get controller DID
187    let service = get_proof_service(&sidetree_doc);
188
189    // Return immediately multiple proof services present
190    if let Err(ResolverError::MultipleTrustchainProofService) = service {
191        return Err(ResolverError::MultipleTrustchainProofService);
192    };
193
194    if let Ok(service) = service {
195        let controller_did = get_from_proof_service(service, "controller");
196
197        // Convert doc
198        let doc = transform_doc(&sidetree_doc, controller_did.unwrap().as_str());
199
200        // Convert metadata
201        let doc_meta = transform_doc_metadata(&sidetree_doc, sidetree_doc_meta);
202
203        // Convert resolution metadata
204        let res_meta = sidetree_res_meta;
205
206        // Return tuple
207        Ok((res_meta, doc, doc_meta))
208    } else {
209        // If proof service is not present, return Ok.
210        Ok((sidetree_res_meta, sidetree_doc, sidetree_doc_meta))
211    }
212}
213
214/// Trait for performing Trustchain resolution.
215#[async_trait]
216pub trait TrustchainResolver: DIDResolver + AsDIDResolver {
217    /// Provides the wrapped resolver of the implementing type.
218    // fn wrapped_resolver<T: DIDResolver + Sync + Send>(&self) -> &T;
219    fn wrapped_resolver(&self) -> &dyn DIDResolver;
220
221    /// Transforms the result of a DID resolution into the Trustchain format.
222    fn transform(
223        &self,
224        (res_meta, doc, doc_meta): (
225            ResolutionMetadata,
226            Option<Document>,
227            Option<DocumentMetadata>,
228        ),
229    ) -> (
230        ResolutionMetadata,
231        Option<Document>,
232        Option<DocumentMetadata>,
233    ) {
234        // If a document and document metadata are returned, try to convert.
235        if let (Some(did_doc), Some(did_doc_meta)) = (doc, doc_meta) {
236            // Convert to trustchain versions.
237            let tc_result = transform_as_result(res_meta, did_doc, did_doc_meta);
238            match tc_result {
239                // Map the tuple of non-option types to have tuple with optional document metadata
240                Ok((tc_res_meta, tc_doc, tc_doc_meta)) => {
241                    (tc_res_meta, Some(tc_doc), Some(tc_doc_meta))
242                }
243                // If cannot convert, return the relevant error
244                Err(ResolverError::FailedToConvertToTrustchain(err)) => {
245                    let res_meta = ResolutionMetadata {
246                        error: Some(err.to_string()),
247                        content_type: None,
248                        property_set: None,
249                    };
250                    (res_meta, None, None)
251                }
252                Err(ResolverError::MultipleTrustchainProofService) => {
253                    let res_meta = ResolutionMetadata {
254                        error: Some("Found multiple Trustchain proof service entries.".to_string()),
255                        content_type: None,
256                        property_set: None,
257                    };
258                    (res_meta, None, None)
259                }
260                Err(err) => {
261                    let res_meta = ResolutionMetadata {
262                        error: Some(err.to_string()),
263                        content_type: None,
264                        property_set: None,
265                    };
266                    (res_meta, None, None)
267                }
268            }
269        } else {
270            // If doc or doc_meta None, return sidetree resolution as is
271            (res_meta, None, None)
272        }
273    }
274
275    /// Sync Trustchain resolve function returning resolution metadata,
276    /// DID document and DID document metadata from a passed DID as a `Result` type.
277    async fn resolve_as_result(&self, did: &str) -> ResolverResult {
278        // sidetree resolved resolution metadata, document and document metadata
279        let (did_res_meta, did_doc, did_doc_meta) =
280            self.resolve(did, &ResolutionInputMetadata::default()).await;
281
282        // Handle error cases based on string content of the resolution metadata
283        if let Some(did_res_meta_error) = &did_res_meta.error {
284            if did_res_meta_error
285                .starts_with("Error sending HTTP request: error sending request for url")
286            {
287                Err(ResolverError::ConnectionFailure)
288            } else if did_res_meta_error == "invalidDid" {
289                Err(ResolverError::NonExistentDID(did.to_string()))
290            } else if did_res_meta_error == "notFound" {
291                Err(ResolverError::DIDNotFound(did.to_string()))
292            } else if did_res_meta_error.contains("Failed to convert to Trustchain") {
293                Err(ResolverError::FailedToConvertToTrustchain(
294                    did_res_meta_error
295                        .to_owned()
296                        .rsplit(':')
297                        .next()
298                        .unwrap_or("")
299                        .to_owned(),
300                ))
301            } else if did_res_meta_error == "Multiple Trustchain proof service entries are present."
302            {
303                Err(ResolverError::MultipleTrustchainProofService)
304            } else {
305                eprintln!("Unhandled error message: {}", did_res_meta_error);
306                let eof_err_msg = "Error parsing resolution response: EOF while parsing a value at line 1 column 0";
307                if did_res_meta_error == eof_err_msg {
308                    eprintln!(
309                        "HINT: If using HTTP for resolution, ensure a valid client is in use."
310                    );
311                }
312                panic!();
313            }
314        } else {
315            Ok((did_res_meta, did_doc, did_doc_meta))
316        }
317    }
318
319    async fn trustchain_resolve(
320        &self,
321        did: &str,
322        input_metadata: &ResolutionInputMetadata,
323    ) -> (
324        ResolutionMetadata,
325        Option<Document>,
326        Option<DocumentMetadata>,
327    ) {
328        // TODO: remove upon handling with DIDMethods
329        if did.starts_with("did:key:") {
330            let did_key_resolver = DIDKey;
331            return did_key_resolver
332                .resolve(did, &ResolutionInputMetadata::default())
333                .await;
334        }
335
336        let resolved = self.wrapped_resolver().resolve(did, input_metadata).await;
337
338        // Consider using ResolutionInputMetadata to optionally not perform transform.
339        // Resolve with the wrapped DIDResolver and then transform to Trustchain format.
340        let transformed = self.transform(resolved);
341        self.extended_transform(transformed).await
342    }
343
344    /// Provides implementors of this trait with a mechanism to perform additional transformations
345    /// when resolving DIDs. By default this is the identity map (no transformations).
346    async fn extended_transform(
347        &self,
348        (res_meta, doc, doc_meta): (
349            ResolutionMetadata,
350            Option<Document>,
351            Option<DocumentMetadata>,
352        ),
353    ) -> (
354        ResolutionMetadata,
355        Option<Document>,
356        Option<DocumentMetadata>,
357    ) {
358        (res_meta, doc, doc_meta)
359    }
360}
361
362// To facilitate trait upcasting: https://stackoverflow.com/a/28664881
363pub trait AsDIDResolver {
364    fn as_did_resolver(&self) -> &dyn DIDResolver;
365}
366
367impl<T: DIDResolver> AsDIDResolver for T {
368    fn as_did_resolver(&self) -> &dyn DIDResolver {
369        self
370    }
371}
372
373#[cfg(test)]
374mod tests {
375    use super::*;
376    use crate::data::{
377        TEST_SIDETREE_DOCUMENT, TEST_SIDETREE_DOCUMENT_METADATA,
378        TEST_SIDETREE_DOCUMENT_MULTIPLE_PROOF, TEST_SIDETREE_DOCUMENT_SERVICE_AND_PROOF,
379        TEST_SIDETREE_DOCUMENT_SERVICE_NOT_PROOF, TEST_SIDETREE_DOCUMENT_WITH_CONTROLLER,
380        TEST_TRUSTCHAIN_DOCUMENT, TEST_TRUSTCHAIN_DOCUMENT_METADATA,
381    };
382    use crate::utils::canonicalize;
383
384    #[test]
385    fn test_add_controller() {
386        // Test add_controller method with successful result.
387
388        let controller_did = "did:ion:test:EiCBr7qGDecjkR2yUBhn3aNJPUR3TSEOlkpNcL0Q5Au9YP";
389
390        // Load a Sidetree-resolved DID Document.
391        let did_doc =
392            Document::from_json(TEST_SIDETREE_DOCUMENT).expect("Document failed to load.");
393
394        // Check there is no controller in the DID document.
395        assert!(did_doc.controller.is_none());
396
397        // Call add_controller on the Resolver to get the result.
398        let result =
399            add_controller(did_doc, controller_did).expect("Different Controller already present.");
400
401        // Check there *is* a controller field in the resulting DID document.
402        assert!(result.controller.is_some());
403        // Check the controller DID is correct.
404        assert_eq!(
405            result.controller,
406            Some(OneOrMany::One(String::from(controller_did)))
407        );
408
409        // Construct the expected result (a DID Document) from a test fixture.
410        let expected = Document::from_json(TEST_SIDETREE_DOCUMENT_WITH_CONTROLLER)
411            .expect("Document failed to load.");
412
413        // Check the resulting DID document matches the expected one.
414        assert_eq!(result, expected);
415    }
416
417    #[test]
418    fn test_add_controller_fail() {
419        // Test add_controller method with failure as controller already present.
420
421        let controller_did = "did:ion:test:EiCBr7qGDecjkR2yUBhn3aNJPUR3TSEOlkpNcL0Q5Au9YP";
422
423        // Construct a DID Document that already contains a controller property.
424        let did_doc = Document::from_json(TEST_SIDETREE_DOCUMENT_WITH_CONTROLLER)
425            .expect("Document failed to load.");
426
427        // Check the controller property is present.
428        assert!(did_doc.controller.is_some());
429
430        // Construct a Resolver instance.
431
432        // Attempt to add the controller.
433        let result = add_controller(did_doc, controller_did);
434
435        // Confirm error.
436        assert!(matches!(
437            result,
438            Err(ResolverError::ControllerAlreadyPresent)
439        ));
440    }
441
442    #[test]
443    fn test_remove_proof_service() {
444        // Test remove_proof_service method with successful result.
445
446        // Load a Sidetree-resolved DID Document.
447        let did_doc =
448            Document::from_json(TEST_SIDETREE_DOCUMENT).expect("Document failed to load.");
449
450        // Check the proof service is present.
451        assert!(did_doc.service.is_some());
452
453        // Remove the proof service in the DID document.
454        let did_doc_no_proof_service = remove_proof_service(did_doc);
455
456        // Check the proof service has been removed.
457        assert!(did_doc_no_proof_service.service.is_none());
458    }
459
460    #[test]
461    fn test_get_proof_service() {
462        // Test get_proof_service method on a sidetree-resolved DID document.
463
464        // Load a Sidetree-resolved DID Document.
465        let did_doc =
466            Document::from_json(TEST_SIDETREE_DOCUMENT).expect("Document failed to load.");
467
468        // Check that precisely one service is present in the DID document.
469        assert_eq!(did_doc.service.as_ref().unwrap().len(), 1_usize);
470
471        // Get the service property containing the Trustchain proof.
472        let proof_service = get_proof_service(&did_doc).unwrap();
473
474        // Check the contents of the proof service property.
475        assert_eq!(proof_service.id, format!("#trustchain-controller-proof"));
476        assert_eq!(
477            proof_service.type_,
478            OneOrMany::One(String::from("TrustchainProofService"))
479        );
480    }
481
482    #[test]
483    fn test_get_proof_service_only() {
484        // Test get_proof_service method when non-proof service is present.
485
486        // Load a Sidetree-resolved DID Document.
487        let did_doc = Document::from_json(TEST_SIDETREE_DOCUMENT_SERVICE_AND_PROOF)
488            .expect("Document failed to load.");
489
490        // Check that two services are present in the DID document.
491        assert_eq!(did_doc.service.as_ref().unwrap().len(), 2_usize);
492
493        // Get the service property containing the Trustchain proof.
494        let proof_service = get_proof_service(&did_doc).unwrap();
495
496        // Check the contents of the proof service property.
497        assert_eq!(proof_service.id, format!("#trustchain-controller-proof"));
498        assert_eq!(
499            proof_service.type_,
500            OneOrMany::One(String::from("TrustchainProofService"))
501        );
502    }
503
504    #[test]
505    fn test_get_proof_service_fail_multiple_proof_services() {
506        // Test get_proof_service method with failure as multiple proof services present.
507
508        // Construct a DID Document with muliple proof services.
509        let did_doc = Document::from_json(TEST_SIDETREE_DOCUMENT_MULTIPLE_PROOF)
510            .expect("Document failed to load.");
511
512        // Check that two services are present in the DID document.
513        assert_eq!(did_doc.service.as_ref().unwrap().len(), 2_usize);
514
515        let result = get_proof_service(&did_doc);
516
517        // Expect an error due to the presence of multiple proof services.
518        assert!(matches!(
519            result,
520            Err(ResolverError::MultipleTrustchainProofService)
521        ));
522    }
523
524    #[test]
525    fn test_get_proof_service_fail_no_proof_services() {
526        // Test get_proof_service method with failure as no proof services present.
527
528        // Construct a DID Document with a service but no proof services.
529        let did_doc = Document::from_json(TEST_SIDETREE_DOCUMENT_SERVICE_NOT_PROOF)
530            .expect("Document failed to load.");
531
532        // Check that a service is present in the DID document.
533        assert!(did_doc.service.is_some());
534
535        let result = get_proof_service(&did_doc);
536
537        // // Expect an error due to the absence of any proof services.
538        assert!(matches!(
539            result,
540            Err(ResolverError::NoTrustchainProofService)
541        ));
542    }
543
544    #[test]
545    fn test_get_proof_service_fail_no_services() {
546        // Test get_proof_service method with failure as no services present.
547
548        // Construct a DID Document with no proof services.
549        let did_doc =
550            Document::from_json(TEST_TRUSTCHAIN_DOCUMENT).expect("Document failed to load.");
551
552        // Check that no services are present in the DID document.
553        assert!(did_doc.service.is_none());
554
555        let result = get_proof_service(&did_doc);
556
557        // Expect an error due to the absence of any proof services.
558        assert!(matches!(
559            result,
560            Err(ResolverError::NoTrustchainProofService)
561        ));
562    }
563
564    #[test]
565    fn test_get_from_proof_service() {
566        // Test to extract the controller DID from the service field in a sidetree-resolved DID document.
567
568        // Load a Sidetree-resolved DID Document.
569        let did_doc =
570            Document::from_json(TEST_SIDETREE_DOCUMENT).expect("Document failed to load.");
571
572        // Get a reference to the proof service.
573        let service = get_proof_service(&did_doc).unwrap();
574
575        // Get the controller DID from the proof service.
576        let controller = get_from_proof_service(service, "controller").unwrap();
577
578        // Check the controller DID matches the expected value.
579        assert_eq!(
580            controller,
581            "did:ion:test:EiCBr7qGDecjkR2yUBhn3aNJPUR3TSEOlkpNcL0Q5Au9ZQ"
582        )
583    }
584
585    #[test]
586    fn test_add_proof() {
587        // Test adding a proof to DID Document Metadata.
588
589        // Load a Sidetree-resolved DID Document.
590        let sidetree_doc =
591            Document::from_json(TEST_SIDETREE_DOCUMENT).expect("Document failed to load doc.");
592
593        // Load Sidetree-resolved DID Document Metadata.
594        let sidetree_meta: DocumentMetadata =
595            serde_json::from_str(TEST_SIDETREE_DOCUMENT_METADATA).expect("Failed to load metadata");
596
597        // Load and canonicalize Trustchain document metadata.
598        let expected_tc_meta: DocumentMetadata =
599            serde_json::from_str(TEST_TRUSTCHAIN_DOCUMENT_METADATA)
600                .expect("Failed to load metadata");
601        let expected_tc_meta =
602            canonicalize(&expected_tc_meta).expect("Cannot add proof and canonicalize.");
603
604        // Add proof to the DID Document Metadata and canonicalize the result.
605        let actual_tc_meta = canonicalize(&add_proof(&sidetree_doc, sidetree_meta))
606            .expect("Cannot add proof and canonicalize.");
607
608        // Check that the result matches the expected metadata.
609        assert_eq!(expected_tc_meta, actual_tc_meta);
610    }
611
612    #[test]
613    fn test_transform_doc_metadata() {
614        // Test transformation of Sidetree-resolved DID Document Metadata to Trustchain format.
615
616        // See https://github.com/alan-turing-institute/trustchain/issues/11
617
618        // Load a Sidetree-resolved DID Document.
619        let did_doc =
620            Document::from_json(TEST_SIDETREE_DOCUMENT).expect("Document failed to load doc.");
621
622        // Construct Sidetree-resolved DID Document Metadata.
623        let sidetree_meta: DocumentMetadata =
624            serde_json::from_str(TEST_SIDETREE_DOCUMENT_METADATA).expect("Failed to load metadata");
625
626        // Transform the DID Document Metadata by resolving into Trustchain format.
627        let actual = transform_doc_metadata(&did_doc, sidetree_meta);
628
629        // Canonicalise the result and compare with the expected Trustchain format.
630        let canon_actual_meta = canonicalize(&actual).expect("Cannot add proof and canonicalize.");
631
632        let tc_meta: DocumentMetadata = serde_json::from_str(TEST_TRUSTCHAIN_DOCUMENT_METADATA)
633            .expect("Failed to load metadata");
634        let canon_tc_meta = canonicalize(&tc_meta).expect("Cannot add proof and canonicalize.");
635
636        assert_eq!(canon_tc_meta, canon_actual_meta);
637    }
638
639    #[test]
640    fn test_transform_doc() {
641        // Test transformation of a Sidetree-resolved DID Document into Trustchain format.
642
643        // Load a Sidetree-resolved DID Document.
644        let did_doc =
645            Document::from_json(TEST_SIDETREE_DOCUMENT).expect("Document failed to load.");
646
647        // Get the controller from the proof service property in the Sidetree-resolved DID document.
648        let proof_service = get_proof_service(&did_doc).unwrap();
649        let controller = get_from_proof_service(proof_service, "controller").unwrap();
650
651        // Transform the DID document by resolving into Trustchain format.
652        let actual = transform_doc(&did_doc, controller.as_str());
653
654        // Canonicalise the result and compare with the expected Trustchain format.
655        let canon_actual_doc = canonicalize(&actual).expect("Failed to canonicalize.");
656
657        let tc_doc =
658            Document::from_json(TEST_TRUSTCHAIN_DOCUMENT).expect("Document failed to load.");
659        let canon_tc_doc = canonicalize(&tc_doc).expect("Failed to canonicalize.");
660
661        assert_eq!(canon_tc_doc, canon_actual_doc);
662    }
663
664    #[test]
665    fn test_transform_as_result() {
666        // Test transformation of Sidetree-resolved DID Document + Metadata into Trustchain format.
667
668        // Construct sample DID documents & metadata from test fixtures.
669        let input_doc =
670            Document::from_json(TEST_SIDETREE_DOCUMENT).expect("Document failed to load.");
671        let expected_output_doc =
672            Document::from_json(TEST_TRUSTCHAIN_DOCUMENT).expect("Document failed to load.");
673        let input_doc_meta: DocumentMetadata =
674            serde_json::from_str(TEST_SIDETREE_DOCUMENT_METADATA)
675                .expect("Document failed to load.");
676        let expected_output_doc_meta: DocumentMetadata =
677            serde_json::from_str(TEST_TRUSTCHAIN_DOCUMENT_METADATA)
678                .expect("Document failed to load.");
679        let input_res_meta = ResolutionMetadata {
680            error: None,
681            content_type: None,
682            property_set: None,
683        };
684        let expected_output_res_meta = ResolutionMetadata {
685            error: None,
686            content_type: None,
687            property_set: None,
688        };
689
690        // Call function and get output result type
691        let output = transform_as_result(input_res_meta, input_doc, input_doc_meta);
692
693        // Result should be Ok variant with returned data
694        if let Ok((actual_output_res_meta, actual_output_doc, actual_output_doc_meta)) = output {
695            // Check resolution metadata is equal
696            assert_eq!(
697                canonicalize(&expected_output_res_meta).unwrap(),
698                canonicalize(&actual_output_res_meta).unwrap()
699            );
700            // Check documents are equal
701            assert_eq!(expected_output_doc, actual_output_doc);
702            // Check document metadata is equal
703            assert_eq!(
704                canonicalize(&expected_output_doc_meta).unwrap(),
705                canonicalize(&actual_output_doc_meta).unwrap()
706            );
707        } else {
708            // If error variant, panic
709            panic!()
710        }
711    }
712
713    #[test]
714    fn transform_as_result_with_multiple_proof_services() {
715        // Test that Trustchain resolution fails in the presence of multiple proof services
716        // (indicating an invalid DID Document).
717
718        // Construct sample DID document & metadata from test fixtures.
719        let input_doc = Document::from_json(TEST_SIDETREE_DOCUMENT_MULTIPLE_PROOF)
720            .expect("Document failed to load.");
721        let input_doc_meta: DocumentMetadata =
722            serde_json::from_str(TEST_SIDETREE_DOCUMENT_METADATA)
723                .expect("Document failed to load.");
724        let input_res_meta = ResolutionMetadata {
725            error: None,
726            content_type: None,
727            property_set: None,
728        };
729
730        // Call the resolve function and get output Result type.
731        let output = transform_as_result(input_res_meta, input_doc, input_doc_meta);
732
733        // Check for the correct error.
734        assert!(matches!(
735            output,
736            Err(ResolverError::MultipleTrustchainProofService)
737        ));
738    }
739}