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