Skip to main content

printobserver_sdk/
error.rs

1//! Every way a call can end other than with the answer it asked for.
2//!
3//! Five of them, and each is a different thing for a caller to do next: the
4//! supervisor was not there, this client would not send the request, the
5//! supervisor said no in words, the policy refused the action, or the machine
6//! itself refused it. The fourth is the one with a shape rather than a
7//! sentence, because a caller that can read the range allowed can ask again
8//! for something inside it.
9
10use crate::contract::{ActionAnswer, PolicyDecision, Range, RejectionReason};
11
12/// The policy's own refusal of one action, as a caller reads it.
13///
14/// [`Rejection::reason`] is the refusal itself, matched rather than read;
15/// [`Rejection::requested`] and [`Rejection::allowed`] are the value that was
16/// asked for and the range that is allowed, which the policy states when it
17/// ruled on a value and which are absent when it ruled on something else.
18#[derive(Debug, Clone, PartialEq)]
19pub struct Rejection {
20    /// Why the action was refused.
21    pub reason: RejectionReason,
22    /// The value that was asked for, where the policy ruled on one.
23    pub requested: Option<f64>,
24    /// The range that is allowed, where the policy ruled on a value.
25    pub allowed: Option<Range>,
26    /// The whole answer, whose record carries the request and the decision.
27    pub answer: Box<ActionAnswer>,
28}
29
30impl Rejection {
31    /// The refusal one answer carries, when the decision on it was a refusal.
32    #[must_use]
33    pub fn of(answer: ActionAnswer) -> Option<Self> {
34        let PolicyDecision::Rejected(reason) = &answer.record.decision else {
35            return None;
36        };
37        let (requested, allowed) = match reason {
38            RejectionReason::OutOfBounds {
39                requested, allowed, ..
40            } => (Some(*requested), Some(allowed.clone())),
41            _ => (None, None),
42        };
43        Some(Self {
44            reason: reason.clone(),
45            requested,
46            allowed,
47            answer: Box::new(answer),
48        })
49    }
50}
51
52impl core::fmt::Display for Rejection {
53    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
54        write!(
55            formatter,
56            "the supervisor's policy refused this action: {:?}",
57            self.reason
58        )?;
59        if let (Some(requested), Some(allowed)) = (self.requested, self.allowed.as_ref()) {
60            write!(
61                formatter,
62                ". It was asked for {requested}, and what is allowed is {} to {}",
63                allowed.min, allowed.max
64            )?;
65        }
66        Ok(())
67    }
68}
69
70/// Why a call did not answer what it asked for.
71#[derive(Debug, Clone, PartialEq)]
72pub enum ClientError {
73    /// Nothing answered at the configured address.
74    Unreachable {
75        /// Where this client looked.
76        address: String,
77        /// What happened there, in the words of whatever failed.
78        detail: String,
79    },
80    /// This client would not send the request, because it carried no reason.
81    ///
82    /// Every mutating operation carries a reason, and a call whose reason is
83    /// empty is refused here rather than at the server: nothing reaches the
84    /// policy, the printer or the record.
85    NoReason,
86    /// A value of the request could not be rendered as JSON.
87    Unsendable {
88        /// What could not be rendered, in the words of whatever refused it.
89        detail: String,
90    },
91    /// The supervisor answered something this client cannot read.
92    Unreadable {
93        /// The status it answered under.
94        status: u16,
95        /// What could not be read, in the words of whatever refused it.
96        detail: String,
97    },
98    /// The supervisor will not do what it was asked, and said why.
99    Refused {
100        /// The status it answered under.
101        status: u16,
102        /// What it said, in its own words.
103        detail: String,
104    },
105    /// The policy accepted the action and the machine refused it.
106    PrinterRefused {
107        /// What the printer said.
108        detail: String,
109        /// The record of the request and the decision taken on it.
110        answer: Box<ActionAnswer>,
111    },
112    /// The policy refused the action, and the refusal is the answer.
113    Rejected(Box<Rejection>),
114}
115
116impl core::fmt::Display for ClientError {
117    fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
118        match self {
119            Self::Unreachable { address, detail } => write!(
120                formatter,
121                "nothing answered at {address}: {detail}. Start the supervisor there, or \
122                 point this client at the address it is answering on"
123            ),
124            Self::NoReason => formatter
125                .write_str("every mutating call carries a reason, and this one carries none"),
126            Self::Unsendable { detail } => {
127                write!(
128                    formatter,
129                    "this request could not be rendered as JSON: {detail}"
130                )
131            }
132            Self::Unreadable { status, detail } => write!(
133                formatter,
134                "the supervisor answered {status} with something this client cannot read: \
135                 {detail}"
136            ),
137            Self::Refused { status, detail } => {
138                write!(formatter, "the supervisor answered {status}: {detail}")
139            }
140            Self::PrinterRefused { detail, .. } => write!(
141                formatter,
142                "the policy accepted this action and the machine refused it: {detail}. Look \
143                 at the printer, then ask again"
144            ),
145            Self::Rejected(rejection) => write!(formatter, "{rejection}"),
146        }
147    }
148}
149
150impl core::error::Error for ClientError {}