use trust_dns_resolver::Resolver; use trust_dns_resolver::config::{ResolverConfig, ResolverOpts}; #[derive(Debug)] pub struct DidDoc { pub did: String, } fn get_txt_did(handle: &String) -> Result { // create a txt resolver let resolver = match Resolver::new(ResolverConfig::default(), ResolverOpts::default()) { Ok(val) => val, Err(_) => return Err(()), }; // resolve _atproto.handle to a TXT record let txt_res = match resolver.txt_lookup("_atproto.".to_owned() + &handle) { Ok(val) => val, Err(_) => return Err(()), // collect all entries and convert to strings } .into_iter() .map(|x| x.to_string()) .collect::>(); // filter entries which do not start with `did=` let did_res = txt_res .clone() .extract_if(.., |x| x.starts_with("did=")) .collect::>(); // only 1 did= can exist // https://atproto.com/specs/handle#:~:text=If%20multiple%20valid%20records%20with%20different%20DIDs%20are%20present,%20resolution%20should%20fail. if did_res.len() != 1 { return Err(()); } let did = did_res[0].clone(); return Ok(did.clone()); } fn get_http_did(handle: &String) -> Result { let res = match reqwest::blocking::get("https://".to_owned() + handle + "/.well-known/atproto-did") { Ok(val) => val, Err(_) => return Err(()) }; // as per spec, non 2xx code means failure if !res.status().is_success() { return Err(()) } let did_unparsed = match res.text() { Ok(val) => val, Err(_) => return Err(()) }; let did = did_unparsed.trim(); if !did.starts_with("did:") { return Err(()) }; return Ok(String::from(did)) } pub fn get_did(handle: String) -> Result { let did = if let Ok(did) = get_txt_did(&handle) { did } else { if let Ok(did) = get_http_did(&handle) { did } else { return Err(()); } }; return Ok(DidDoc { did }); }