1use http::{uri::Scheme, Uri};
3use serde::{Deserialize, Serialize};
4
5#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
6#[serde(rename_all = "camelCase")]
7pub struct DidDocument {
8 #[serde(skip_serializing_if = "Option::is_none")]
9 #[serde(rename = "@context")]
10 pub context: Option<Vec<String>>,
11 pub id: String,
12 #[serde(skip_serializing_if = "Option::is_none")]
13 pub also_known_as: Option<Vec<String>>,
14 #[serde(skip_serializing_if = "Option::is_none")]
15 pub verification_method: Option<Vec<VerificationMethod>>,
16 #[serde(skip_serializing_if = "Option::is_none")]
17 pub service: Option<Vec<Service>>,
18}
19
20#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
21#[serde(rename_all = "camelCase")]
22pub struct VerificationMethod {
23 pub id: String,
24 pub r#type: String,
25 pub controller: String,
26 #[serde(skip_serializing_if = "Option::is_none")]
27 pub public_key_multibase: Option<String>,
28}
29
30#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)]
31#[serde(rename_all = "camelCase")]
32pub struct Service {
33 pub id: String,
34 pub r#type: String,
35 pub service_endpoint: String,
36}
37
38impl DidDocument {
39 pub fn get_pds_endpoint(&self) -> Option<String> {
40 self.get_service_endpoint("#atproto_pds", "AtprotoPersonalDataServer")
41 }
42 pub fn get_feed_gen_endpoint(&self) -> Option<String> {
43 self.get_service_endpoint("#bsky_fg", "BskyFeedGenerator")
44 }
45 pub fn get_notif_endpoint(&self) -> Option<String> {
46 self.get_service_endpoint("#bsky_notif", "BskyNotificationService")
47 }
48 fn get_service_endpoint(&self, id: &str, r#type: &str) -> Option<String> {
49 let full_id = self.id.to_string() + id;
50 if let Some(services) = &self.service {
51 let service_endpoint = services
52 .iter()
53 .find(|service| {
54 (service.id == id || service.id == full_id) && service.r#type == r#type
55 })
56 .map(|service| service.service_endpoint.clone())?;
57 return Some(service_endpoint).filter(|s| Self::validate_url(s));
58 }
59 None
60 }
61 fn validate_url(url: &str) -> bool {
62 url.parse::<Uri>()
63 .map(|uri| match uri.scheme() {
64 Some(scheme) if (scheme == &Scheme::HTTP || scheme == &Scheme::HTTPS) => {
65 uri.host().is_some()
66 }
67 _ => false,
68 })
69 .unwrap_or_default()
70 }
71 pub fn get_signing_key(&self) -> Option<&VerificationMethod> {
72 self.verification_method.as_ref().and_then(|methods| {
73 methods.iter().find(|method| {
74 method.id == "#atproto" || method.id == format!("{}#atproto", self.id)
75 })
76 })
77 }
78}