pub(crate) const SVC_DIDCOMM: &str = "DIDCommMessaging";
pub(crate) const SVC_WEBVH_HOSTING: &str = "WebVHHosting";
pub(crate) const SVC_WEBVH_HOSTING_LEGACY: &str = "WebVHHostingService";
pub(crate) trait ServiceEntry {
fn types(&self) -> &[String];
fn endpoint_uri(&self) -> Option<String>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub(crate) enum ResolvedTransport {
DIDComm,
Rest { url: String },
}
pub(crate) fn resolve_server_transport<S: ServiceEntry>(
services: &[S],
) -> Option<ResolvedTransport> {
if services.iter().any(|s| s.types().iter().any(is_didcomm)) {
return Some(ResolvedTransport::DIDComm);
}
for svc in services {
if svc.types().iter().any(is_webvh_rest)
&& let Some(raw) = svc.endpoint_uri()
{
let url = raw.trim_matches('"').trim_end_matches('/').to_string();
if url.is_empty() {
continue;
}
return Some(ResolvedTransport::Rest { url });
}
}
None
}
#[inline]
fn is_didcomm(t: &String) -> bool {
t == SVC_DIDCOMM
}
#[inline]
fn is_webvh_rest(t: &String) -> bool {
t == SVC_WEBVH_HOSTING || t == SVC_WEBVH_HOSTING_LEGACY
}
pub(crate) const SUPPORTED_TYPES_HUMAN: &str =
"DIDCommMessaging, WebVHHosting, or WebVHHostingService (legacy)";
impl ServiceEntry for affinidi_tdk::did_common::service::Service {
fn types(&self) -> &[String] {
&self.type_
}
fn endpoint_uri(&self) -> Option<String> {
self.service_endpoint.get_uri()
}
}
#[cfg(test)]
mod tests {
use super::*;
struct TestService {
types: Vec<String>,
uri: Option<String>,
}
impl TestService {
fn new(types: &[&str], uri: Option<&str>) -> Self {
Self {
types: types.iter().map(|s| s.to_string()).collect(),
uri: uri.map(String::from),
}
}
}
impl ServiceEntry for TestService {
fn types(&self) -> &[String] {
&self.types
}
fn endpoint_uri(&self) -> Option<String> {
self.uri.clone()
}
}
#[test]
fn empty_service_list_yields_none() {
let services: Vec<TestService> = vec![];
assert_eq!(resolve_server_transport(&services), None);
}
#[test]
fn unsupported_service_type_yields_none() {
let services = vec![TestService::new(&["LinkedDomains"], Some("https://x"))];
assert_eq!(resolve_server_transport(&services), None);
}
#[test]
fn didcomm_only_resolves_to_didcomm() {
let services = vec![TestService::new(&[SVC_DIDCOMM], None)];
assert_eq!(
resolve_server_transport(&services),
Some(ResolvedTransport::DIDComm)
);
}
#[test]
fn webvh_hosting_canonical_resolves_to_rest() {
let services = vec![TestService::new(
&[SVC_WEBVH_HOSTING],
Some("https://daemon.example"),
)];
assert_eq!(
resolve_server_transport(&services),
Some(ResolvedTransport::Rest {
url: "https://daemon.example".into()
})
);
}
#[test]
fn webvh_hosting_service_legacy_alias_accepted() {
let services = vec![TestService::new(
&[SVC_WEBVH_HOSTING_LEGACY],
Some("https://legacy.example"),
)];
assert_eq!(
resolve_server_transport(&services),
Some(ResolvedTransport::Rest {
url: "https://legacy.example".into()
})
);
}
#[test]
fn didcomm_wins_when_listed_first() {
let services = vec![
TestService::new(&[SVC_DIDCOMM], None),
TestService::new(&[SVC_WEBVH_HOSTING], Some("https://x")),
];
assert_eq!(
resolve_server_transport(&services),
Some(ResolvedTransport::DIDComm)
);
}
#[test]
fn didcomm_wins_when_listed_after_rest() {
let services = vec![
TestService::new(&[SVC_WEBVH_HOSTING], Some("https://x")),
TestService::new(&[SVC_DIDCOMM], None),
];
assert_eq!(
resolve_server_transport(&services),
Some(ResolvedTransport::DIDComm)
);
}
#[test]
fn rest_url_strips_surrounding_quotes() {
let services = vec![TestService::new(
&[SVC_WEBVH_HOSTING],
Some("\"https://daemon.example\""),
)];
assert_eq!(
resolve_server_transport(&services),
Some(ResolvedTransport::Rest {
url: "https://daemon.example".into()
})
);
}
#[test]
fn rest_url_strips_trailing_slash() {
let services = vec![TestService::new(
&[SVC_WEBVH_HOSTING],
Some("https://daemon.example/"),
)];
assert_eq!(
resolve_server_transport(&services),
Some(ResolvedTransport::Rest {
url: "https://daemon.example".into()
})
);
}
#[test]
fn rest_url_strips_quotes_and_trailing_slash_together() {
let services = vec![TestService::new(
&[SVC_WEBVH_HOSTING],
Some("\"https://daemon.example/\""),
)];
assert_eq!(
resolve_server_transport(&services),
Some(ResolvedTransport::Rest {
url: "https://daemon.example".into()
})
);
}
#[test]
fn rest_entry_without_endpoint_falls_through() {
let services = vec![
TestService::new(&[SVC_WEBVH_HOSTING], None),
TestService::new(&[SVC_WEBVH_HOSTING], Some("https://second.example")),
];
assert_eq!(
resolve_server_transport(&services),
Some(ResolvedTransport::Rest {
url: "https://second.example".into()
})
);
}
#[test]
fn rest_entry_with_empty_uri_after_trim_is_skipped() {
let services = vec![TestService::new(&[SVC_WEBVH_HOSTING], Some("/"))];
assert_eq!(resolve_server_transport(&services), None);
}
#[test]
fn multi_typed_service_entry_matches_any_type() {
let services = vec![TestService::new(
&["LinkedDomains", SVC_WEBVH_HOSTING],
Some("https://multi.example"),
)];
assert_eq!(
resolve_server_transport(&services),
Some(ResolvedTransport::Rest {
url: "https://multi.example".into()
})
);
}
}