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}