openagent_auth_protocol/error.rs
1//! Error types for the OpenAgent Core Protocol identity flow.
2//!
3//! Every fallible operation in this crate returns [`AuthProtocolError`].
4//! Variants are designed to be **actionable** — error messages name the
5//! operation, the field, and what went wrong.
6//!
7//! # History
8//!
9//! This crate previously carried two error enums (`AuthProtocolError` and
10//! `OaapError`) that disagreed with each other — which is part of how the
11//! crate came to not compile. There is one now.
12
13use thiserror::Error;
14
15/// Errors arising from Core Protocol handshake operations.
16#[derive(Debug, Error)]
17pub enum AuthProtocolError {
18 /// The protocol version in the received message is not supported.
19 #[error(
20 "unsupported protocol version {received}; this implementation supports version {supported}"
21 )]
22 UnsupportedVersion {
23 /// The version received from the peer.
24 received: u8,
25 /// The version this implementation supports.
26 supported: u8,
27 },
28
29 /// A challenge failed the Section 4 field constraints.
30 #[error("invalid challenge: {reason}")]
31 InvalidChallenge {
32 /// What constraint failed.
33 reason: String,
34 },
35
36 /// A proof failed the Section 15.3 shape checks.
37 #[error("invalid proof: {reason}")]
38 InvalidProof {
39 /// What constraint failed.
40 reason: String,
41 },
42
43 /// The nonce echoed in the proof does not match the challenge.
44 #[error("nonce mismatch: challenge carried {expected}, proof echoed {received}")]
45 NonceMismatch {
46 /// The nonce in the challenge.
47 expected: String,
48 /// The nonce echoed in the proof.
49 received: String,
50 },
51
52 /// The challenge nonce has expired (anti-replay protection).
53 #[error("challenge expired: issued at {issued}, expiry was {expiry}, current time is {now}")]
54 ChallengeExpired {
55 /// When the challenge was issued.
56 issued: String,
57 /// When it expired.
58 expiry: String,
59 /// Current timestamp.
60 now: String,
61 },
62
63 /// A nonce was reused (replay attack detected).
64 #[error("replay detected: nonce {nonce_hex} has already been used in a prior handshake")]
65 ReplayDetected {
66 /// Hex-encoded nonce that was replayed.
67 nonce_hex: String,
68 },
69
70 /// Signature verification failed against the trusted key set.
71 #[error("signature verification failed: {reason}")]
72 SignatureVerificationFailed {
73 /// Why verification failed.
74 reason: String,
75 },
76
77 /// The peer's DID document is structurally invalid or missing required fields.
78 #[error("invalid DID document for {did}: {reason}")]
79 InvalidDidDocument {
80 /// The DID whose document was invalid.
81 did: String,
82 /// What was wrong.
83 reason: String,
84 },
85
86 /// The transport layer failed to send or receive a message.
87 #[error("transport error: {reason}")]
88 TransportFailed {
89 /// Underlying transport error.
90 reason: String,
91 },
92
93 /// A timeout occurred waiting for a response from the peer.
94 #[error("timeout waiting for {step} response after {timeout_secs}s")]
95 Timeout {
96 /// Which protocol step timed out.
97 step: String,
98 /// How long we waited.
99 timeout_secs: u64,
100 },
101
102 /// Serialization or deserialization of a protocol message failed.
103 #[error("message serialization failed for {message_type}: {reason}")]
104 SerializationFailed {
105 /// The message type being (de)serialized.
106 message_type: String,
107 /// Underlying failure reason.
108 reason: String,
109 },
110
111 /// The challenge expiry duration exceeds the maximum allowed (300 seconds,
112 /// per Section 7 of the Core Protocol Specification).
113 #[error("challenge expiry {requested_secs}s exceeds maximum allowed {max_secs}s")]
114 ExpiryTooLong {
115 /// The requested expiry in seconds.
116 requested_secs: u64,
117 /// The maximum allowed.
118 max_secs: u64,
119 },
120
121 /// An internal failure that is not the peer's fault (e.g. a poisoned
122 /// lock in a session store). Surfaced rather than panicked, per the
123 /// workspace no-panic policy.
124 #[error("internal error: {0}")]
125 Internal(String),
126
127 /// A presented session token does not identify a live session.
128 #[error("session not found: {session_id}")]
129 SessionNotFound {
130 /// The token or identifier presented.
131 session_id: String,
132 },
133
134 /// A session exists but has expired and was removed.
135 #[error("session expired: {session_id}")]
136 SessionExpired {
137 /// The token or identifier presented.
138 session_id: String,
139 },
140}