use http::StatusCode;
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum InvalidInput {
#[error("an identifier root is not an ISO OID")]
Oid,
#[error("the patient identifier value is empty")]
EmptyValue,
#[error(
"the responding gateway endpoint is not an https URL without a fragment (http is accepted for development only)"
)]
Endpoint,
#[error("the XUA assertion is not one SAML 2.0 Assertion element")]
Assertion,
}
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum XcpdError {
#[error("the responding gateway answered a SOAP fault ({code})")]
Fault {
code: FaultCode,
status: StatusCode,
},
#[error("the responding gateway could not satisfy the request")]
ApplicationError {
issues: Vec<DetectedIssue>,
},
#[error("the responding gateway refused the query parameters")]
QueryRefused,
#[error("the responding gateway answered {status}")]
Rejected {
status: StatusCode,
},
#[error("the responding gateway did not answer within the timeout")]
Timeout,
#[error("the responding gateway could not be reached")]
Transport(#[source] reqwest::Error),
#[error("the ITI-55 request could not be written")]
Encode(#[source] std::io::Error),
#[error("the responding gateway's answer does not hold to ITI-55")]
Malformed(#[from] Malformation),
#[error("the ITI-55 audit message could not be recorded")]
Audit(#[source] super::audit::AuditError),
}
impl XcpdError {
#[must_use]
pub fn status(&self) -> Option<StatusCode> {
match self {
Self::Fault { status, .. } | Self::Rejected { status } => Some(*status),
Self::ApplicationError { .. } | Self::QueryRefused | Self::Malformed(_) => {
Some(StatusCode::OK)
}
Self::Timeout | Self::Transport(_) | Self::Encode(_) | Self::Audit(_) => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum FaultCode {
VersionMismatch,
MustUnderstand,
DataEncodingUnknown,
Sender,
Receiver,
Other,
}
impl FaultCode {
pub(super) fn of(value: &str) -> Self {
let local = value.rsplit(':').next().unwrap_or(value).trim();
match local {
"VersionMismatch" => Self::VersionMismatch,
"MustUnderstand" => Self::MustUnderstand,
"DataEncodingUnknown" => Self::DataEncodingUnknown,
"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::VersionMismatch => "VersionMismatch",
Self::MustUnderstand => "MustUnderstand",
Self::DataEncodingUnknown => "DataEncodingUnknown",
Self::Sender => "Sender",
Self::Receiver => "Receiver",
Self::Other => "a code SOAP 1.2 does not define",
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum DetectedIssue {
ResponderBusy,
AnswerNotAvailable,
InternalError,
Other,
}
impl DetectedIssue {
pub(super) fn of(code: &str, system: Option<&str>) -> Self {
if system != Some(super::ISSUE_SYSTEM) {
return Self::Other;
}
match code {
"ResponderBusy" => Self::ResponderBusy,
"AnswerNotAvailable" => Self::AnswerNotAvailable,
"InternalError" => Self::InternalError,
_ => Self::Other,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum Malformation {
#[error("the answer is not application/soap+xml")]
NotSoap,
#[error("the answer is a multipart package")]
Multipart,
#[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 carries a document type declaration")]
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 SOAP body holds neither an ITI-55 response nor a fault")]
UnexpectedBody,
#[error("the response's interactionId is not PRPA_IN201306UV02")]
WrongInteraction,
#[error("the answer relates to another request")]
RelatesToAnother,
#[error("the response has no valid acknowledgement type code")]
Acknowledgement,
#[error("the response has no valid query response code")]
QueryResponseCode,
#[error("a no-match response holds registration events")]
MatchesWithoutMatch,
#[error("an OK response holds neither a match nor a request for attributes")]
EmptyMatch,
#[error("registration event {event} names no homeCommunityId")]
NoHomeCommunity {
event: usize,
},
#[error("registration event {event} has no patient id")]
NoPatientId {
event: usize,
},
}