atrium_api/
did_doc.rs

1//! Definitions for DID document types.
2use 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}