Skip to main content

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}