use serde_json::{Value, json};
use crate::protocol::matching::{DIDCOMM_SERVICE_TYPE, TSP_SERVICE_TYPE};
use super::TemplateError;
pub const DIDCOMM_SERVICE_VAR: &str = "SERVICE_DIDCOMM";
pub const TSP_SERVICE_VAR: &str = "SERVICE_TSP";
const DIDCOMM_FRAGMENT: &str = "#didcomm";
const TSP_FRAGMENT: &str = "#tsp";
pub fn didcomm_service(mediator_did: &str) -> Result<Value, TemplateError> {
transport_service(
mediator_did,
DIDCOMM_SERVICE_TYPE,
DIDCOMM_FRAGMENT,
"DIDComm",
)
}
pub fn tsp_service(mediator_did: &str) -> Result<Value, TemplateError> {
transport_service(mediator_did, TSP_SERVICE_TYPE, TSP_FRAGMENT, "TSP")
}
fn transport_service(
mediator_did: &str,
type_: &str,
fragment: &str,
label: &str,
) -> Result<Value, TemplateError> {
let mediator_did = mediator_did.trim();
if !mediator_did.starts_with("did:") {
return Err(TemplateError::Invalid(format!(
"{label} service must name the mediator by DID, got '{mediator_did}'. The \
serviceEndpoint is the mediator's DID — the transport URL lives in the \
mediator's own document — so a URL here renders an entry no sender can route."
)));
}
Ok(json!({
"id": format!("{{DID}}{fragment}"),
"type": type_,
"serviceEndpoint": mediator_did,
}))
}
#[cfg(test)]
mod tests {
#![allow(clippy::unwrap_used, clippy::expect_used)]
use super::*;
const MEDIATOR: &str = "did:webvh:QmTS3:webvh.example.com:mediator";
#[test]
fn didcomm_entry_names_the_mediator_by_did() {
let e = didcomm_service(MEDIATOR).unwrap();
assert_eq!(e["id"], "{DID}#didcomm");
assert_eq!(e["type"], DIDCOMM_SERVICE_TYPE);
assert_eq!(e["serviceEndpoint"], MEDIATOR);
}
#[test]
fn tsp_entry_names_the_mediator_by_did() {
let e = tsp_service(MEDIATOR).unwrap();
assert_eq!(e["id"], "{DID}#tsp");
assert_eq!(e["type"], TSP_SERVICE_TYPE);
assert_eq!(e["serviceEndpoint"], MEDIATOR);
}
#[test]
fn both_transports_bind_the_same_mediator() {
assert_eq!(
didcomm_service(MEDIATOR).unwrap()["serviceEndpoint"],
tsp_service(MEDIATOR).unwrap()["serviceEndpoint"]
);
}
#[test]
fn a_url_endpoint_is_refused() {
for build in [
didcomm_service as fn(&str) -> Result<Value, TemplateError>,
tsp_service,
] {
let err = build("https://mediator.example.com").unwrap_err();
let msg = err.to_string();
assert!(msg.contains("by DID"), "must say what is wrong: {msg}");
}
}
#[test]
fn surrounding_whitespace_does_not_defeat_the_did_check() {
let e = tsp_service(&format!(" {MEDIATOR} ")).unwrap();
assert_eq!(e["serviceEndpoint"], MEDIATOR);
}
#[test]
fn an_empty_mediator_is_refused() {
assert!(didcomm_service("").is_err());
assert!(tsp_service(" ").is_err());
}
#[test]
fn the_id_carries_the_did_sentinel_for_late_substitution() {
assert!(
tsp_service(MEDIATOR).unwrap()["id"]
.as_str()
.unwrap()
.starts_with("{DID}")
);
}
}