Skip to main content

auths_core/ports/
network.rs

1//! Network port and DID resolution.
2
3use std::future::Future;
4
5use auths_verifier::core::Ed25519PublicKey;
6use auths_verifier::keri::Prefix;
7
8/// Domain error for outbound network operations.
9///
10/// Adapters map transport-specific failures (e.g., HTTP timeouts, connection
11/// refused) into these variants. Domain logic never sees transport details.
12///
13/// Usage:
14/// ```ignore
15/// use auths_core::ports::network::NetworkError;
16///
17/// fn handle(err: NetworkError) {
18///     match err {
19///         NetworkError::Unreachable { endpoint } => eprintln!("cannot reach {endpoint}"),
20///         NetworkError::Timeout { endpoint } => eprintln!("timed out: {endpoint}"),
21///         NetworkError::NotFound { resource } => eprintln!("missing: {resource}"),
22///         NetworkError::Unauthorized => eprintln!("not authorized"),
23///         NetworkError::InvalidResponse { detail } => eprintln!("bad response: {detail}"),
24///         NetworkError::Internal(inner) => eprintln!("bug: {inner}"),
25///     }
26/// }
27/// ```
28#[derive(Debug, thiserror::Error)]
29pub enum NetworkError {
30    /// The endpoint could not be reached.
31    #[error("endpoint unreachable: {endpoint}")]
32    Unreachable {
33        /// The unreachable endpoint URL.
34        endpoint: String,
35    },
36
37    /// The request timed out.
38    #[error("request timed out: {endpoint}")]
39    Timeout {
40        /// The endpoint that timed out.
41        endpoint: String,
42    },
43
44    /// The requested resource was not found.
45    #[error("resource not found: {resource}")]
46    NotFound {
47        /// The missing resource identifier.
48        resource: String,
49    },
50
51    /// Authentication or authorisation failed.
52    #[error("unauthorized")]
53    Unauthorized,
54
55    /// The server returned an unexpected response.
56    #[error("invalid response: {detail}")]
57    InvalidResponse {
58        /// Details about the invalid response.
59        detail: String,
60    },
61
62    /// An unexpected internal error.
63    #[error("internal network error: {0}")]
64    Internal(Box<dyn std::error::Error + Send + Sync>),
65}
66
67/// Domain error for identity resolution operations.
68///
69/// Distinguishes resolution-specific failures (unknown DID, revoked key)
70/// from general transport failures via the `Network` variant.
71///
72/// Usage:
73/// ```ignore
74/// use auths_core::ports::network::ResolutionError;
75///
76/// fn handle(err: ResolutionError) {
77///     match err {
78///         ResolutionError::DidNotFound { did } => eprintln!("unknown: {did}"),
79///         ResolutionError::InvalidDid { did, reason } => eprintln!("{did}: {reason}"),
80///         ResolutionError::KeyRevoked { did } => eprintln!("revoked: {did}"),
81///         ResolutionError::Network(inner) => eprintln!("transport: {inner}"),
82///     }
83/// }
84/// ```
85#[derive(Debug, thiserror::Error)]
86pub enum ResolutionError {
87    /// The DID was not found.
88    #[error("DID not found: {did}")]
89    DidNotFound {
90        /// The DID that was not found.
91        did: String,
92    },
93
94    /// The DID is malformed.
95    #[error("invalid DID {did}: {reason}")]
96    InvalidDid {
97        /// The malformed DID.
98        did: String,
99        /// Reason the DID is invalid.
100        reason: String,
101    },
102
103    /// The key for this DID has been revoked.
104    #[error("key revoked for DID: {did}")]
105    KeyRevoked {
106        /// The DID whose key was revoked.
107        did: String,
108    },
109
110    /// A network error occurred during resolution.
111    #[error("network error: {0}")]
112    Network(#[from] NetworkError),
113}
114
115/// Cryptographic material resolved from a decentralized identifier.
116///
117/// Usage:
118/// ```ignore
119/// use auths_core::ports::network::ResolvedIdentity;
120///
121/// let identity: ResolvedIdentity = resolver.resolve_identity("did:key:z...").await?;
122/// let pk = identity.public_key();
123/// ```
124#[derive(Debug, Clone)]
125pub enum ResolvedIdentity {
126    /// Static did:key (no rotation possible).
127    Key {
128        /// The resolved DID string.
129        did: String,
130        /// The Ed25519 public key.
131        public_key: Ed25519PublicKey,
132    },
133    /// KERI-based identity with rotation capability.
134    Keri {
135        /// The resolved DID string.
136        did: String,
137        /// The Ed25519 public key.
138        public_key: Ed25519PublicKey,
139        /// Current KEL sequence number.
140        sequence: u64,
141        /// Whether key rotation is available.
142        can_rotate: bool,
143    },
144}
145
146impl ResolvedIdentity {
147    /// Returns the DID string.
148    pub fn did(&self) -> &str {
149        match self {
150            ResolvedIdentity::Key { did, .. } | ResolvedIdentity::Keri { did, .. } => did,
151        }
152    }
153
154    /// Returns the Ed25519 public key.
155    pub fn public_key(&self) -> &Ed25519PublicKey {
156        match self {
157            ResolvedIdentity::Key { public_key, .. }
158            | ResolvedIdentity::Keri { public_key, .. } => public_key,
159        }
160    }
161
162    /// Returns `true` if this is a `did:key` resolution.
163    pub fn is_key(&self) -> bool {
164        matches!(self, ResolvedIdentity::Key { .. })
165    }
166
167    /// Returns `true` if this is a `did:keri` resolution.
168    pub fn is_keri(&self) -> bool {
169        matches!(self, ResolvedIdentity::Keri { .. })
170    }
171}
172
173/// Resolves a decentralized identifier to its current cryptographic material.
174///
175/// Implementations may fetch data from local stores, remote registries, or
176/// peer-to-peer networks. The domain only provides a DID string and receives
177/// the resolved key material.
178///
179/// Usage:
180/// ```ignore
181/// use auths_core::ports::network::IdentityResolver;
182///
183/// async fn verify_signer(resolver: &dyn IdentityResolver, did: &str) -> Vec<u8> {
184///     let resolved = resolver.resolve_identity(did).await.unwrap();
185///     resolved.public_key
186/// }
187/// ```
188pub trait IdentityResolver: Send + Sync {
189    /// Resolves a DID string to its current public key and method metadata.
190    ///
191    /// Args:
192    /// * `did`: The decentralized identifier to resolve (e.g., `"did:keri:EAbcdef..."`).
193    ///
194    /// Usage:
195    /// ```ignore
196    /// let identity = resolver.resolve_identity("did:key:z6Mk...").await?;
197    /// ```
198    fn resolve_identity(
199        &self,
200        did: &str,
201    ) -> impl Future<Output = Result<ResolvedIdentity, ResolutionError>> + Send;
202}
203
204/// Submits key events and queries receipts from the witness infrastructure.
205///
206/// Witnesses observe and receipt key events to provide accountability.
207/// Implementations handle the transport details; the domain provides
208/// serialized events and receives receipts as opaque byte arrays.
209///
210/// Usage:
211/// ```ignore
212/// use auths_core::ports::network::WitnessClient;
213///
214/// async fn witness_inception(client: &dyn WitnessClient, endpoint: &str, event: &[u8]) {
215///     let receipt = client.submit_event(endpoint, event).await.unwrap();
216/// }
217/// ```
218pub trait WitnessClient: Send + Sync {
219    /// Submits a serialized key event to a witness and returns the receipt bytes.
220    ///
221    /// Args:
222    /// * `endpoint`: The witness endpoint identifier.
223    /// * `event`: The serialized key event bytes.
224    ///
225    /// Usage:
226    /// ```ignore
227    /// let receipt = client.submit_event("witness-1.example.com", &event_bytes).await?;
228    /// ```
229    fn submit_event(
230        &self,
231        endpoint: &str,
232        event: &[u8],
233    ) -> impl Future<Output = Result<Vec<u8>, NetworkError>> + Send;
234
235    /// Queries all receipts a witness holds for the given KERI prefix.
236    ///
237    /// Args:
238    /// * `endpoint`: The witness endpoint identifier.
239    /// * `prefix`: The KERI prefix to query receipts for.
240    ///
241    /// Usage:
242    /// ```ignore
243    /// let prefix = Prefix::new_unchecked("EAbcdef...".into());
244    /// let receipts = client.query_receipts("witness-1.example.com", &prefix).await?;
245    /// ```
246    fn query_receipts(
247        &self,
248        endpoint: &str,
249        prefix: &Prefix,
250    ) -> impl Future<Output = Result<Vec<Vec<u8>>, NetworkError>> + Send;
251}
252
253/// Response from a registry POST operation.
254///
255/// Carries the HTTP status code and body so callers can dispatch on
256/// status-specific business logic (e.g., 201 Created vs. 409 Conflict).
257#[derive(Debug)]
258pub struct RegistryResponse {
259    /// HTTP status code.
260    pub status: u16,
261    /// Response body bytes.
262    pub body: Vec<u8>,
263}
264
265/// Fetches and pushes data to a remote registry service.
266///
267/// Implementations handle the transport protocol (e.g., HTTP, gRPC).
268/// The domain provides logical paths and receives raw bytes.
269///
270/// Usage:
271/// ```ignore
272/// use auths_core::ports::network::RegistryClient;
273///
274/// async fn sync_identity(client: &dyn RegistryClient, url: &str) {
275///     let data = client.fetch_registry_data(url, "identities/abc123").await.unwrap();
276/// }
277/// ```
278pub trait RegistryClient: Send + Sync {
279    /// Fetches data from a registry at the given logical path.
280    ///
281    /// Args:
282    /// * `registry_url`: The registry service identifier.
283    /// * `path`: The logical path within the registry.
284    ///
285    /// Usage:
286    /// ```ignore
287    /// let data = client.fetch_registry_data("registry.example.com", "identities/abc123").await?;
288    /// ```
289    fn fetch_registry_data(
290        &self,
291        registry_url: &str,
292        path: &str,
293    ) -> impl Future<Output = Result<Vec<u8>, NetworkError>> + Send;
294
295    /// Pushes data to a registry at the given logical path.
296    ///
297    /// Args:
298    /// * `registry_url`: The registry service identifier.
299    /// * `path`: The logical path within the registry.
300    /// * `data`: The raw bytes to push.
301    ///
302    /// Usage:
303    /// ```ignore
304    /// client.push_registry_data("registry.example.com", "identities/abc123", &bytes).await?;
305    /// ```
306    fn push_registry_data(
307        &self,
308        registry_url: &str,
309        path: &str,
310        data: &[u8],
311    ) -> impl Future<Output = Result<(), NetworkError>> + Send;
312
313    /// POSTs a JSON payload to a registry endpoint and returns the raw response.
314    ///
315    /// Args:
316    /// * `registry_url`: Base URL of the registry service.
317    /// * `path`: The logical path within the registry (e.g., `"v1/identities"`).
318    /// * `json_body`: Serialized JSON bytes to send as the request body.
319    ///
320    /// Usage:
321    /// ```ignore
322    /// let resp = client.post_json("https://registry.example.com", "v1/identities", &body).await?;
323    /// match resp.status {
324    ///     201 => { /* success */ }
325    ///     409 => { /* conflict */ }
326    ///     _ => { /* error */ }
327    /// }
328    /// ```
329    fn post_json(
330        &self,
331        registry_url: &str,
332        path: &str,
333        json_body: &[u8],
334    ) -> impl Future<Output = Result<RegistryResponse, NetworkError>> + Send;
335}