use fhir_types::r4::audit_event::AuditEvent;
use fhir_types::r4::bundle::{Bundle, BundleEntry, BundleEntrySearch, BundleLink};
use fhir_types::r4::operation_outcome::{OperationOutcome, OperationOutcomeIssue};
use fhir_types::r4::primitives;
use fhir_types::r4::resource::Resource;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Match {
pub full_url: Option<String>,
pub resource: AuditEvent,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum IssueType {
Invalid,
Login,
Forbidden,
NotFound,
NotSupported,
TooCostly,
Transient,
Exception,
}
impl IssueType {
#[must_use]
pub const fn code(self) -> &'static str {
match self {
Self::Invalid => "invalid",
Self::Login => "login",
Self::Forbidden => "forbidden",
Self::NotFound => "not-found",
Self::NotSupported => "not-supported",
Self::TooCostly => "too-costly",
Self::Transient => "transient",
Self::Exception => "exception",
}
}
}
#[must_use]
pub fn searchset(
total: u32,
self_url: &str,
next_url: Option<&str>,
matches: Vec<Match>,
) -> Bundle {
let mut link = vec![BundleLink {
relation: primitives::String::from("self"),
url: primitives::Uri::from(self_url),
..BundleLink::default()
}];
if let Some(next) = next_url {
link.push(BundleLink {
relation: primitives::String::from("next"),
url: primitives::Uri::from(next),
..BundleLink::default()
});
}
Bundle {
r#type: primitives::Code::from("searchset"),
total: Some(primitives::UnsignedInt::from(total)),
link,
entry: matches.into_iter().map(entry).collect(),
..Bundle::default()
}
}
fn entry(found: Match) -> BundleEntry {
BundleEntry {
full_url: found.full_url.map(primitives::Uri::from),
resource: Some(Resource::AuditEvent(Box::new(found.resource))),
search: Some(BundleEntrySearch {
mode: Some(primitives::Code::from("match")),
..BundleEntrySearch::default()
}),
..BundleEntry::default()
}
}
#[must_use]
pub fn refusal(code: IssueType, diagnostics: &str) -> OperationOutcome {
OperationOutcome {
issue: vec![OperationOutcomeIssue {
severity: primitives::Code::from("error"),
code: primitives::Code::from(code.code()),
diagnostics: Some(primitives::String::from(diagnostics)),
..OperationOutcomeIssue::default()
}],
..OperationOutcome::default()
}
}
#[cfg(test)]
mod tests {
use serde_json::json;
use super::*;
#[test]
fn an_empty_page_is_a_searchset_with_its_total_and_self_link() {
let bundle = searchset(
0,
"https://arr.example.org/AuditEvent?date=ge2013-01-01",
None,
Vec::new(),
);
assert_eq!(
serde_json::to_value(&bundle).expect("serializes"),
json!({
"resourceType": "Bundle",
"type": "searchset",
"total": 0,
"link": [{
"relation": "self",
"url": "https://arr.example.org/AuditEvent?date=ge2013-01-01"
}]
})
);
}
#[test]
fn a_match_is_an_entry_in_match_mode_with_a_next_link() {
let bundle = searchset(
3,
"https://arr.example.org/AuditEvent?date=2013&_count=1",
Some("https://arr.example.org/AuditEvent?date=2013&_count=1&_offset=1"),
vec![Match {
full_url: Some("https://arr.example.org/AuditEvent/23".into()),
resource: AuditEvent {
id: Some("23".into()),
..AuditEvent::default()
},
}],
);
let value = serde_json::to_value(&bundle).expect("serializes");
assert_eq!(value["total"], 3);
assert_eq!(value["link"][1]["relation"], "next");
assert_eq!(value["entry"][0]["search"]["mode"], "match");
assert_eq!(
value["entry"][0]["fullUrl"],
"https://arr.example.org/AuditEvent/23"
);
assert_eq!(value["entry"][0]["resource"]["resourceType"], "AuditEvent");
}
#[test]
fn a_refusal_is_one_error_issue() {
let outcome = refusal(IssueType::Invalid, "the query names no date parameter");
assert_eq!(
serde_json::to_value(&outcome).expect("serializes"),
json!({
"resourceType": "OperationOutcome",
"issue": [{
"severity": "error",
"code": "invalid",
"diagnostics": "the query names no date parameter"
}]
})
);
}
}