Skip to main content

kmp_memory_api/
api_error.rs

1use std::fmt;
2
3use serde::{Deserialize, Serialize};
4
5/// How this contract fails.
6///
7/// Three shapes, because a consumer acts differently on each: waiting is a
8/// remedy for `Unavailable`, asking about something else is the remedy for
9/// `NotFound`, and `Refused` means the kernel looked at the request and said
10/// no — retrying it unchanged earns the same answer.
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
12pub enum ApiError {
13    Unavailable { reason: String },
14    NotFound { what: String },
15    Refused { reason: String },
16}
17
18impl ApiError {
19    /// Whether trying again, unchanged, could plausibly succeed.
20    ///
21    /// Published on the error rather than left to the consumer, because a
22    /// consumer keeping its own table of which errors are worth retrying goes
23    /// stale the first time this enum grows.
24    pub fn is_transient(&self) -> bool {
25        matches!(self, Self::Unavailable { .. })
26    }
27}
28
29impl fmt::Display for ApiError {
30    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
31        match self {
32            Self::Unavailable { reason } => write!(f, "the memory kernel is unavailable: {reason}"),
33            Self::NotFound { what } => write!(f, "not found: {what}"),
34            Self::Refused { reason } => write!(f, "the memory kernel refused: {reason}"),
35        }
36    }
37}
38
39impl std::error::Error for ApiError {}
40
41#[cfg(test)]
42mod tests {
43    use super::*;
44
45    #[test]
46    fn only_unavailability_invites_a_retry() {
47        assert!(
48            ApiError::Unavailable {
49                reason: "opening".to_string()
50            }
51            .is_transient()
52        );
53        assert!(
54            !ApiError::NotFound {
55                what: "about project:x".to_string()
56            }
57            .is_transient()
58        );
59        assert!(
60            !ApiError::Refused {
61                reason: "empty about".to_string()
62            }
63            .is_transient(),
64            "retrying a refusal unchanged asks the same question and earns the same answer"
65        );
66    }
67}