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 {}