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