use crate::contract::{ActionAnswer, PolicyDecision, Range, RejectionReason, SpoolCommandAnswer};
#[derive(Debug, Clone, PartialEq, serde::Serialize)]
#[serde(untagged)]
pub enum RejectedAnswer {
Action(Box<ActionAnswer>),
SpoolCommand(Box<SpoolCommandAnswer>),
}
#[derive(Debug, Clone, PartialEq)]
pub struct Rejection {
pub reason: RejectionReason,
pub requested: Option<f64>,
pub allowed: Option<Range>,
pub answer: RejectedAnswer,
}
impl Rejection {
#[must_use]
pub fn of(answer: RejectedAnswer) -> Option<Self> {
let decision = match &answer {
RejectedAnswer::Action(action) => &action.record.decision,
RejectedAnswer::SpoolCommand(command) => &command.decision,
};
let PolicyDecision::Rejected(reason) = decision else {
return None;
};
let (requested, allowed) = match reason {
RejectionReason::OutOfBounds {
requested, allowed, ..
} => (Some(*requested), Some(allowed.clone())),
_ => (None, None),
};
Some(Self {
reason: reason.clone(),
requested,
allowed,
answer,
})
}
}
impl core::fmt::Display for Rejection {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(
formatter,
"the supervisor's policy refused this action: {:?}",
self.reason
)?;
if let (Some(requested), Some(allowed)) = (self.requested, self.allowed.as_ref()) {
write!(
formatter,
". It was asked for {requested}, and what is allowed is {} to {}",
allowed.min, allowed.max
)?;
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq)]
pub enum ClientError {
Unreachable {
address: String,
detail: String,
},
NoReason,
Unsendable {
detail: String,
},
Unreadable {
status: u16,
detail: String,
},
Refused {
status: u16,
detail: String,
},
PrinterRefused {
detail: String,
answer: Box<ActionAnswer>,
},
Rejected(Box<Rejection>),
}
impl core::fmt::Display for ClientError {
fn fmt(&self, formatter: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Unreachable { address, detail } => write!(
formatter,
"nothing answered at {address}: {detail}. Start the supervisor there, or \
point this client at the address it is answering on"
),
Self::NoReason => formatter
.write_str("every mutating call carries a reason, and this one carries none"),
Self::Unsendable { detail } => {
write!(
formatter,
"this request could not be rendered as JSON: {detail}"
)
}
Self::Unreadable { status, detail } => write!(
formatter,
"the supervisor answered {status} with something this client cannot read: \
{detail}"
),
Self::Refused { status, detail } => {
write!(formatter, "the supervisor answered {status}: {detail}")
}
Self::PrinterRefused { detail, .. } => write!(
formatter,
"the policy accepted this action and the machine refused it: {detail}. Look \
at the printer, then ask again"
),
Self::Rejected(rejection) => write!(formatter, "{rejection}"),
}
}
}
impl core::error::Error for ClientError {}