use http::StatusCode;
use super::question::DataCategory;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum InvalidInput {
#[error(
"the Mitz endpoint is not an https URL without userinfo, query or fragment (http is accepted for development only)"
)]
Endpoint,
#[error("the BSN is empty")]
EmptyBsn,
#[error("a code is empty or has leading or trailing white space")]
Code,
#[error("an identifier root is not an ISO OID")]
Oid,
#[error("a professional's identification number is not 1 to 60 alphanumeric characters")]
ProfessionalNumber,
#[error("the question asks about no data category")]
NoCategory,
#[error("the question asks about data category {0} twice")]
DuplicateCategory(DataCategory),
#[error("the question asks about more than {limit} data categories")]
TooManyCategories {
limit: usize,
},
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum ClientError {
#[error("the Mitz endpoint is refused")]
Endpoint(#[source] InvalidInput),
#[error("the HTTP client for Mitz could not be built")]
Build(#[source] reqwest::Error),
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum MitzError {
#[error("Mitz answered a SOAP fault ({code})")]
Fault {
code: FaultCode,
status: StatusCode,
},
#[error("Mitz answered {status}")]
Rejected {
status: StatusCode,
},
#[error("Mitz did not answer within the timeout")]
Timeout,
#[error("Mitz could not be reached")]
Transport(#[source] reqwest::Error),
#[error("the closed authorization question could not be written")]
Encode(#[source] std::io::Error),
#[error("Mitz answered Indeterminate for data category {category}")]
Indeterminate {
category: DataCategory,
},
#[error("Mitz answered NotApplicable for data category {category}")]
NotApplicable {
category: DataCategory,
},
#[error("Mitz's answer does not hold to the closed authorization question")]
Malformed(#[from] Malformation),
}
impl MitzError {
#[must_use]
pub fn status(&self) -> Option<StatusCode> {
match self {
Self::Fault { status, .. } | Self::Rejected { status } => Some(*status),
Self::Indeterminate { .. } | Self::NotApplicable { .. } | Self::Malformed(_) => {
Some(StatusCode::OK)
}
Self::Timeout | Self::Transport(_) | Self::Encode(_) => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum FaultCode {
Sender,
Receiver,
Other,
}
impl FaultCode {
pub(super) fn of(text: &str) -> Self {
match text.rsplit(':').next().unwrap_or_default() {
"Sender" => Self::Sender,
"Receiver" => Self::Receiver,
_ => Self::Other,
}
}
}
impl std::fmt::Display for FaultCode {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
Self::Sender => "Sender",
Self::Receiver => "Receiver",
Self::Other => "another code",
})
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum Malformation {
#[error("the answer is not application/soap+xml")]
NotSoap,
#[error("the answer exceeds {limit} bytes")]
TooLarge {
limit: usize,
},
#[error("the answer is not well-formed XML (byte {position})")]
NotXml {
position: u64,
},
#[error("the answer declares a document type")]
DocumentType,
#[error("the answer nests deeper than {limit} elements")]
TooDeep {
limit: usize,
},
#[error("the answer is not a SOAP 1.2 envelope with a body")]
NotAnEnvelope,
#[error("the body holds no single XACML 3.0 Response")]
NoResponse,
#[error("result {result} carries no single Decision")]
NoDecision {
result: usize,
},
#[error("result {result} carries an unknown Decision")]
UnknownDecision {
result: usize,
},
#[error("result {result} names no single data category")]
NoCategory {
result: usize,
},
#[error("result {result} is about a data category not asked about")]
UnaskedCategory {
result: usize,
},
#[error("result {result} repeats a data category")]
DuplicateResult {
result: usize,
},
#[error("no result is about data category {0}")]
MissingResult(DataCategory),
#[error("result {result} is not about the patient asked about")]
OtherPatient {
result: usize,
},
#[error("result {result} is not about the data holder asked about")]
OtherHolder {
result: usize,
},
}
pub(super) fn transport(error: reqwest::Error) -> MitzError {
let error = error.without_url();
if error.is_timeout() {
MitzError::Timeout
} else {
MitzError::Transport(error)
}
}