use fhir_types::codec::DecodeError;
use crate::outcome::IssueType;
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum DirectoryError {
#[error("the directory content is not JSON (line {line}, column {column})")]
NotJson {
line: usize,
column: usize,
},
#[error("the directory content is not a FHIR Bundle")]
NotABundle,
#[error("the Bundle does not decode as FHIR R4")]
Decode(#[source] DecodeError),
#[error("the Bundle's type {found:?} is neither collection nor searchset")]
BundleType {
found: Option<String>,
},
#[error("entry {index} has no resource")]
NoResource {
index: usize,
},
#[error("entry {index} holds a resource that is neither an Organization nor an Endpoint")]
UnexpectedEntry {
index: usize,
},
#[error("entry {index} carries a modifierExtension")]
ModifierExtension {
index: usize,
},
#[error("entry {index} repeats the fullUrl of an earlier entry")]
DuplicateFullUrl {
index: usize,
},
#[error("entry {index} repeats the logical id of an earlier resource of its type")]
DuplicateId {
index: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
#[error("the directory base URL is not an http(s) URL without a query or a fragment")]
pub struct InvalidBase;
#[derive(Debug, thiserror::Error)]
#[non_exhaustive]
pub enum McsdError {
#[error("the directory answered {status}")]
Rejected {
status: http::StatusCode,
issues: Vec<IssueType>,
},
#[error("the page link points away from the directory")]
ForeignPage,
#[error("the answer runs to more than {limit} pages")]
TooManyPages {
limit: usize,
},
#[error("the answer runs to more than {limit} bytes")]
TooLarge {
limit: usize,
},
#[error("the answer holds more than {limit} entries")]
TooManyEntries {
limit: usize,
},
#[error("the directory did not answer before the deadline")]
Timeout,
#[error("the directory could not be reached")]
Transport(#[source] reqwest::Error),
#[error("the request to the directory could not be authenticated")]
Unauthenticated(#[source] crate::authorizer::AuthorizerError),
#[error("the directory's answer does not hold to mCSD")]
Malformed(#[from] Malformation),
#[cfg(feature = "balp")]
#[error("the mCSD audit record could not be recorded")]
Audit(#[source] ihe_atna::balp::AuditError),
}
impl McsdError {
#[must_use]
pub fn status(&self) -> Option<http::StatusCode> {
match self {
Self::Rejected { status, .. } => Some(*status),
_ => None,
}
}
#[must_use]
pub fn answered(&self) -> bool {
#[cfg(feature = "balp")]
if matches!(self, Self::Audit(_)) {
return false;
}
!matches!(self, Self::Timeout | Self::Transport(_))
}
#[must_use]
pub fn exceeded(&self) -> bool {
matches!(
self,
Self::Timeout
| Self::TooManyPages { .. }
| Self::TooLarge { .. }
| Self::TooManyEntries { .. }
)
}
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
#[non_exhaustive]
pub enum Malformation {
#[error("the answer is not application/fhir+json")]
NotFhirJson,
#[error("the answer is not JSON (line {line}, column {column})")]
NotJson {
line: usize,
column: usize,
},
#[error("the answer is not a FHIR resource")]
NotAResource,
#[error("the answer is not a Bundle")]
UnexpectedResource,
#[error("the Bundle does not decode as FHIR R4: {kind}")]
Decode {
kind: fhir_types::codec::DecodeErrorKind,
},
#[error("the Bundle is not of type {expected}")]
BundleType {
expected: &'static str,
},
#[error("entry {index} has no fullUrl")]
NoFullUrl {
index: usize,
},
#[error("entry {index} has no resource")]
NoResource {
index: usize,
},
#[error("entry {index} holds a resource of another type than the one asked for")]
UnexpectedEntry {
index: usize,
},
#[error("entry {index} holds a resource with no logical id")]
NoLogicalId {
index: usize,
},
#[error("history entry {index} has no request")]
NoRequest {
index: usize,
},
#[error("history entry {index} has a method a history does not record")]
Method {
index: usize,
},
#[error("history entry {index} deletes no resource of the type asked for")]
RequestUrl {
index: usize,
},
#[error("the next link is not a URL")]
NextLink,
}