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)); } fn parse_doc(did: String, text: String) -> Result { return Ok(DidDoc { did: String::new() }); } fn get_plc_doc(plc: &str) -> Result { let res = match reqwest::blocking::get("https://plc.directory/did:plc".to_owned() + plc) { Ok(val) => val, Err(_) => return Err(()), }; if !res.status().is_success() { return Err(()); } return parse_doc( "did:plc:".to_owned() + plc, match res.text() { Ok(val) => val, Err(_) => return Err(()), }, ); } fn get_web_doc(web: &str) -> Result { let res = match reqwest::blocking::get("https://".to_owned() + web + "/.well-known/did.json") { Ok(val) => val, Err(_) => return Err(()), }; if !res.status().is_success() { return Err(()); } return parse_doc( "did:web:".to_owned() + web, match res.text() { Ok(val) => val, Err(_) => return Err(()), }, ); } 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(()); } }; let did_doc = if did.starts_with("did:plc:") { get_plc_doc(&did[8..]) } else if did.starts_with("did:web:") { get_web_doc(&did[8..]) } else { Err(()) }; return did_doc; }