use crate::session::{VtaEndpoint, resolve_vta_endpoint};
use super::error::ProvisionError;
#[derive(Clone, Debug, Default)]
pub struct ResolvedVta {
pub vta_did: String,
pub tsp_mediator_did: Option<String>,
pub mediator_did: Option<String>,
pub rest_url: Option<String>,
}
impl ResolvedVta {
#[must_use]
pub fn advertised(&self) -> Vec<crate::protocol::matching::Protocol> {
use crate::protocol::matching::Protocol;
let mut out = Vec::with_capacity(3);
if self.tsp_mediator_did.is_some() {
out.push(Protocol::Tsp);
}
if self.mediator_did.is_some() {
out.push(Protocol::Didcomm);
}
if self.rest_url.is_some() {
out.push(Protocol::Rest);
}
out
}
}
pub async fn resolve_vta(vta_did: &str) -> Result<ResolvedVta, ProvisionError> {
flatten(
vta_did,
resolve_vta_endpoint(vta_did)
.await
.map_err(|e| ProvisionError::Resolve {
vta_did: vta_did.to_string(),
message: e.to_string(),
})?,
)
}
pub async fn resolve_vta_with_resolver(
vta_did: &str,
resolver: &affinidi_did_resolver_cache_sdk::DIDCacheClient,
) -> Result<ResolvedVta, ProvisionError> {
flatten(
vta_did,
crate::session::resolve_vta_endpoint_with_resolver(vta_did, resolver)
.await
.map_err(|e| ProvisionError::Resolve {
vta_did: vta_did.to_string(),
message: e.to_string(),
})?,
)
}
fn flatten(vta_did: &str, endpoint: VtaEndpoint) -> Result<ResolvedVta, ProvisionError> {
match endpoint {
VtaEndpoint::Tsp {
vta_did,
mediator_did,
didcomm_mediator_did,
rest_url,
} => Ok(ResolvedVta {
vta_did,
tsp_mediator_did: Some(mediator_did),
mediator_did: didcomm_mediator_did,
rest_url,
}),
VtaEndpoint::DIDComm {
vta_did,
mediator_did,
rest_url,
} => Ok(ResolvedVta {
vta_did,
tsp_mediator_did: None,
mediator_did: Some(mediator_did),
rest_url,
}),
VtaEndpoint::Rest { url } => Ok(ResolvedVta {
vta_did: vta_did.to_string(),
tsp_mediator_did: None,
mediator_did: None,
rest_url: Some(url),
}),
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::protocol::matching::Protocol;
#[allow(unused_imports)]
use crate::provision_client::{resolve_vta, resolve_vta_with_resolver};
fn resolved(tsp: Option<&str>, didcomm: Option<&str>, rest: Option<&str>) -> ResolvedVta {
ResolvedVta {
vta_did: "did:webvh:scid:vta.example".into(),
tsp_mediator_did: tsp.map(str::to_string),
mediator_did: didcomm.map(str::to_string),
rest_url: rest.map(str::to_string),
}
}
#[test]
fn a_dual_transport_vta_reports_every_transport_it_offers() {
let r = resolved(
Some("did:webvh:scid:mediator.example"),
Some("did:webvh:scid:mediator.example"),
Some("https://vta.example"),
);
assert_eq!(
r.advertised(),
vec![Protocol::Tsp, Protocol::Didcomm, Protocol::Rest]
);
}
#[test]
fn a_tsp_only_vta_reports_tsp() {
let r = resolved(Some("did:webvh:scid:mediator.example"), None, None);
assert_eq!(r.advertised(), vec![Protocol::Tsp]);
}
#[test]
fn advertised_is_in_preference_order() {
let r = resolved(None, Some("did:webvh:scid:m"), Some("https://vta.example"));
assert_eq!(r.advertised(), vec![Protocol::Didcomm, Protocol::Rest]);
}
#[test]
fn a_vta_advertising_nothing_reports_nothing() {
assert!(resolved(None, None, None).advertised().is_empty());
}
}
#[cfg(test)]
mod seeded_discovery_tests {
use super::*;
use crate::protocol::matching::Protocol;
use affinidi_did_resolver_cache_sdk::{DIDCacheClient, config::DIDCacheConfigBuilder};
use serde_json::json;
const VTA: &str = "did:web:vta.example";
const MEDIATOR: &str = "did:web:mediator.example";
const REST: &str = "https://vta.example/api";
async fn resolver_serving(doc: serde_json::Value) -> DIDCacheClient {
let mut client = DIDCacheClient::new(DIDCacheConfigBuilder::default().build())
.await
.expect("local DID cache");
client
.add_did_document(
VTA,
serde_json::from_value(doc).expect("fixture document deserializes"),
)
.await;
client
}
fn document(services: serde_json::Value) -> serde_json::Value {
json!({
"@context": ["https://www.w3.org/ns/did/v1"],
"id": VTA,
"service": services,
})
}
#[tokio::test]
async fn a_tsp_advertising_vta_is_discovered_as_tsp() {
let resolver = resolver_serving(document(json!([
{ "id": format!("{VTA}#tsp"), "type": "TSPTransport", "serviceEndpoint": MEDIATOR },
{ "id": format!("{VTA}#vta-didcomm"), "type": "DIDCommMessaging", "serviceEndpoint": MEDIATOR },
])))
.await;
let resolved = resolve_vta_with_resolver(VTA, &resolver)
.await
.expect("the seeded document resolves");
assert_eq!(resolved.tsp_mediator_did.as_deref(), Some(MEDIATOR));
assert_eq!(resolved.mediator_did.as_deref(), Some(MEDIATOR));
assert_eq!(
resolved.advertised(),
vec![Protocol::Tsp, Protocol::Didcomm],
"both transports are reported, in preference order"
);
}
#[tokio::test]
async fn a_didcomm_only_vta_advertises_no_tsp() {
let resolver = resolver_serving(document(json!([
{ "id": format!("{VTA}#vta-didcomm"), "type": "DIDCommMessaging", "serviceEndpoint": MEDIATOR },
{ "id": format!("{VTA}#vta-rest"), "type": "VTARest", "serviceEndpoint": REST },
])))
.await;
let resolved = resolve_vta_with_resolver(VTA, &resolver)
.await
.expect("the seeded document resolves");
assert_eq!(resolved.tsp_mediator_did, None);
assert_eq!(
resolved.advertised(),
vec![Protocol::Didcomm, Protocol::Rest]
);
}
#[tokio::test]
async fn a_tsp_only_vta_does_not_fall_back_to_a_guessed_rest_url() {
let resolver = resolver_serving(document(json!([
{ "id": format!("{VTA}#tsp"), "type": "TSPTransport", "serviceEndpoint": MEDIATOR },
])))
.await;
let resolved = resolve_vta_with_resolver(VTA, &resolver)
.await
.expect("the seeded document resolves");
assert_eq!(resolved.tsp_mediator_did.as_deref(), Some(MEDIATOR));
assert_eq!(resolved.rest_url, None, "no URL was invented");
assert_eq!(resolved.advertised(), vec![Protocol::Tsp]);
}
#[tokio::test]
async fn a_non_did_tsp_endpoint_is_not_treated_as_a_mediator() {
let resolver = resolver_serving(document(json!([
{ "id": format!("{VTA}#tsp"), "type": "TSPTransport", "serviceEndpoint": "https://not-a-did.example" },
{ "id": format!("{VTA}#vta-didcomm"), "type": "DIDCommMessaging", "serviceEndpoint": MEDIATOR },
])))
.await;
let resolved = resolve_vta_with_resolver(VTA, &resolver)
.await
.expect("the seeded document resolves");
assert_eq!(resolved.tsp_mediator_did, None);
assert_eq!(resolved.mediator_did.as_deref(), Some(MEDIATOR));
}
}