use affinidi_did_common::Document;
use affinidi_did_resolver_cache_sdk::DIDCacheClient;
use agent_names::extract_agent_names;
use super::{DisplayName, NameSource};
pub const MAX_CLAIMS_CHECKED: usize = 4;
#[must_use]
pub fn claimed_names(doc: &Document) -> Vec<String> {
extract_agent_names(doc)
.into_iter()
.take(MAX_CLAIMS_CHECKED)
.map(|n| n.as_str().to_string())
.collect()
}
#[must_use]
pub fn select<'a>(
did: &str,
outcomes: impl IntoIterator<Item = (&'a str, Option<&'a str>)>,
) -> Option<DisplayName> {
let mut first_claim: Option<&str> = None;
for (name, resolved) in outcomes {
if resolved == Some(did) {
return Some(DisplayName::new(
name,
NameSource::AgentName { verified: true },
));
}
first_claim.get_or_insert(name);
}
first_claim.map(|name| DisplayName::new(name, NameSource::AgentName { verified: false }))
}
pub async fn lookup(client: &DIDCacheClient, did: &str) -> Option<DisplayName> {
let resolved = client.resolve(did).await.ok()?;
let claims = claimed_names(&resolved.doc);
if claims.is_empty() {
return None;
}
let mut outcomes: Vec<(String, Option<String>)> = Vec::with_capacity(claims.len());
for name in claims {
let landed = client.resolve_any(&name).await.ok().map(|r| r.did);
let matched = landed.as_deref() == Some(did);
outcomes.push((name, landed));
if matched {
break; }
}
select(
did,
outcomes.iter().map(|(n, d)| (n.as_str(), d.as_deref())),
)
}
#[cfg(test)]
mod tests {
use super::*;
const OURS: &str = "did:webvh:QmScidAbCdEfGh:example.com";
const THEIRS: &str = "did:webvh:QmOtherXyZ123:evil.example";
#[test]
fn a_round_tripping_claim_is_verified() {
let name = select(OURS, [("https://example.com/@alice", Some(OURS))]).unwrap();
assert_eq!(name.source, NameSource::AgentName { verified: true });
assert_eq!(name.name, "https://example.com/@alice");
assert!(name.is_trusted());
}
#[test]
fn a_claim_that_lands_on_another_did_is_not_verified() {
let name = select(THEIRS, [("https://mybank.com/@treasury", Some(OURS))]).unwrap();
assert_eq!(name.source, NameSource::AgentName { verified: false });
assert!(
!name.is_trusted(),
"a name belonging to somebody else must never be shown as this DID's"
);
}
#[test]
fn a_claim_that_does_not_resolve_is_not_verified() {
let name = select(OURS, [("https://gone.example/@alice", None)]).unwrap();
assert_eq!(name.source, NameSource::AgentName { verified: false });
}
#[test]
fn the_verified_claim_wins_over_earlier_failures() {
let name = select(
OURS,
[
("https://gone.example/@alice", None),
("https://mybank.com/@treasury", Some(THEIRS)),
("https://example.com/@real", Some(OURS)),
],
)
.unwrap();
assert_eq!(name.source, NameSource::AgentName { verified: true });
assert_eq!(name.name, "https://example.com/@real");
}
#[test]
fn with_no_verified_claim_the_first_is_reported_unverified() {
let name = select(
OURS,
[
("https://mybank.com/@treasury", Some(THEIRS)),
("https://other.example/@x", None),
],
)
.unwrap();
assert_eq!(name.source, NameSource::AgentName { verified: false });
assert_eq!(name.name, "https://mybank.com/@treasury");
}
#[test]
fn no_claims_means_no_name() {
assert!(select(OURS, []).is_none());
}
#[test]
fn a_document_claiming_nothing_yields_no_names() {
let doc = Document::default();
assert!(claimed_names(&doc).is_empty());
}
}