sla_escrow_api/resolution.rs
1/// Resolution reason for oracle verdicts.
2///
3/// Follows Solana's `ProgramError::Custom(u32)` pattern: well-known variants
4/// for cross-oracle interoperability, plus `Custom(u16)` for domain-specific
5/// codes that each oracle defines independently.
6///
7/// On-chain encoding (u16):
8/// 0 = None (default for approvals)
9/// 1-255 = Standard well-known reasons (interoperable across all oracles)
10/// 256+ = Custom oracle-specific codes (meaning defined by each oracle)
11///
12/// # For Oracle Developers
13///
14/// Use standard variants when your rejection reason maps to a well-known
15/// category. Use `Custom(code)` for domain-specific reasons:
16///
17/// ```rust
18/// use sla_escrow_api::resolution::ResolutionReason;
19///
20/// // Standard: any oracle can emit this
21/// let reason = ResolutionReason::LatencyExceeded;
22///
23/// // Custom: only your oracle defines what 1001 means
24/// let reason = ResolutionReason::Custom(1001);
25/// ```
26#[derive(Debug, Clone, Copy, PartialEq, Eq)]
27pub enum ResolutionReason {
28 /// No reason — used for approvals or when no specific reason applies.
29 None,
30 /// HTTP status code fell outside the SLA-specified range.
31 StatusCodeOutOfRange,
32 /// Response latency exceeded the SLA threshold.
33 LatencyExceeded,
34 /// Response body failed JSON Schema validation.
35 SchemaValidationFailed,
36 /// One or more required fields were missing from the response.
37 RequiredFieldsMissing,
38 /// Response body was shorter than the minimum length.
39 BodyTooShort,
40 /// Delivery evidence hash did not match the on-chain commitment.
41 HashMismatch,
42 /// Off-chain evidence could not be fetched or was unavailable.
43 EvidenceUnavailable,
44 /// General/unspecified rejection (catch-all for standard rejections).
45 GeneralRejection,
46 /// Oracle-specific reason code. Each oracle defines its own code meanings.
47 /// Values should be >= `CUSTOM_REASON_BASE` (256).
48 Custom(u16),
49}
50
51/// Boundary between standard and custom reason codes.
52/// Codes 0-255 are reserved for well-known interoperable reasons.
53/// Codes 256-65535 are available for oracle-specific semantics.
54pub const CUSTOM_REASON_BASE: u16 = 256;
55
56impl From<ResolutionReason> for u16 {
57 fn from(reason: ResolutionReason) -> u16 {
58 match reason {
59 ResolutionReason::None => 0,
60 ResolutionReason::StatusCodeOutOfRange => 1,
61 ResolutionReason::LatencyExceeded => 2,
62 ResolutionReason::SchemaValidationFailed => 3,
63 ResolutionReason::RequiredFieldsMissing => 4,
64 ResolutionReason::BodyTooShort => 5,
65 ResolutionReason::HashMismatch => 6,
66 ResolutionReason::EvidenceUnavailable => 7,
67 ResolutionReason::GeneralRejection => 255,
68 ResolutionReason::Custom(code) => code,
69 }
70 }
71}
72
73impl From<u16> for ResolutionReason {
74 fn from(code: u16) -> Self {
75 match code {
76 0 => Self::None,
77 1 => Self::StatusCodeOutOfRange,
78 2 => Self::LatencyExceeded,
79 3 => Self::SchemaValidationFailed,
80 4 => Self::RequiredFieldsMissing,
81 5 => Self::BodyTooShort,
82 6 => Self::HashMismatch,
83 7 => Self::EvidenceUnavailable,
84 255 => Self::GeneralRejection,
85 code => Self::Custom(code),
86 }
87 }
88}
89
90impl std::fmt::Display for ResolutionReason {
91 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
92 match self {
93 Self::None => write!(f, "none"),
94 Self::StatusCodeOutOfRange => write!(f, "status_code_out_of_range"),
95 Self::LatencyExceeded => write!(f, "latency_exceeded"),
96 Self::SchemaValidationFailed => write!(f, "schema_validation_failed"),
97 Self::RequiredFieldsMissing => write!(f, "required_fields_missing"),
98 Self::BodyTooShort => write!(f, "body_too_short"),
99 Self::HashMismatch => write!(f, "hash_mismatch"),
100 Self::EvidenceUnavailable => write!(f, "evidence_unavailable"),
101 Self::GeneralRejection => write!(f, "general_rejection"),
102 Self::Custom(code) => write!(f, "custom({})", code),
103 }
104 }
105}
106
107impl ResolutionReason {
108 /// Returns true if this is a standard (well-known) reason code.
109 pub fn is_standard(&self) -> bool {
110 !matches!(self, Self::Custom(_))
111 }
112
113 /// Returns true if this is a custom oracle-specific reason code.
114 pub fn is_custom(&self) -> bool {
115 matches!(self, Self::Custom(_))
116 }
117
118 /// Returns the raw u16 code.
119 pub fn code(&self) -> u16 {
120 u16::from(*self)
121 }
122}