use crate::concept::{Concept, Designation, Match, Property};
use crate::error::{Outcome, UpstreamError};
#[derive(Debug)]
#[non_exhaustive]
pub enum LookupOutcome {
Found {
name: String,
version: Option<String>,
display: String,
designations: Vec<Designation>,
properties: Vec<Property>,
},
NotFound {
outcome: Box<UpstreamError>,
},
}
#[derive(Debug)]
#[non_exhaustive]
pub enum TranslateOutcome {
Translated {
matches: Vec<Match>,
message: Option<String>,
},
NoMatch {
message: Option<String>,
},
}
impl TranslateOutcome {
pub fn accepted(&self) -> impl Iterator<Item = &Match> {
let matches = match self {
Self::Translated { matches, .. } => matches.as_slice(),
Self::NoMatch { .. } => &[],
};
matches.iter().filter(|found| found.accepted())
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum ValidateOutcome {
Valid {
display: Option<String>,
message: Option<String>,
},
Invalid {
message: Option<String>,
outcome: Option<Outcome>,
},
}
impl ValidateOutcome {
#[must_use]
pub fn is_valid(&self) -> bool {
matches!(self, Self::Valid { .. })
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum Request {
Lookup {
system: String,
code: String,
version: Option<String>,
},
Translate(Translation),
ValidateCode(Validation),
}
impl Request {
#[must_use]
pub const fn operation(&self) -> &'static str {
match self {
Self::Lookup { .. } => crate::LOOKUP,
Self::Translate(_) => crate::TRANSLATE,
Self::ValidateCode(_) => crate::VALIDATE_CODE,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Translation {
pub system: String,
pub code: String,
pub version: Option<String>,
pub concept_map_url: Option<String>,
pub target_system: Option<String>,
pub reverse: Option<bool>,
}
impl Translation {
#[must_use]
pub fn new(system: impl Into<String>, code: impl Into<String>) -> Self {
Self {
system: system.into(),
code: code.into(),
version: None,
concept_map_url: None,
target_system: None,
reverse: None,
}
}
#[must_use]
pub fn with_version(mut self, version: impl Into<String>) -> Self {
self.version = Some(version.into());
self
}
#[must_use]
pub fn with_concept_map(mut self, url: impl Into<String>) -> Self {
self.concept_map_url = Some(url.into());
self
}
#[must_use]
pub fn with_target_system(mut self, system: impl Into<String>) -> Self {
self.target_system = Some(system.into());
self
}
#[must_use]
pub const fn with_reverse(mut self, reverse: bool) -> Self {
self.reverse = Some(reverse);
self
}
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Validation {
pub value_set_url: String,
pub value_set_version: Option<String>,
pub coded: Coded,
}
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[non_exhaustive]
pub enum Coded {
Code {
system: String,
code: String,
version: Option<String>,
display: Option<String>,
},
Coding(Concept),
CodeableConcept {
coding: Vec<Concept>,
text: Option<String>,
},
}
#[derive(Debug)]
#[non_exhaustive]
pub enum BatchOutcome {
Lookup(LookupOutcome),
Translate(TranslateOutcome),
ValidateCode(ValidateOutcome),
}
#[cfg(test)]
mod tests {
use super::{Request, TranslateOutcome, ValidateOutcome};
use crate::concept::{Concept, Equivalence, Match};
fn found(equivalence: &str, code: &str) -> Match {
Match {
equivalence: Some(Equivalence::new(equivalence)),
concept: Some(Concept {
system: Some("http://example.org/target".to_owned()),
code: Some(code.to_owned()),
..Concept::default()
}),
source: None,
}
}
#[test]
fn accepted_yields_only_the_equivalent_and_equal_matches() {
let outcome = TranslateOutcome::Translated {
matches: vec![
found("wider", "broad"),
found("equivalent", "same"),
found("inexact", "close"),
found("equal", "identical"),
],
message: None,
};
let accepted: Vec<Option<String>> = outcome
.accepted()
.map(|found| {
found
.concept
.as_ref()
.and_then(|concept| concept.code.clone())
})
.collect();
assert_eq!(
vec![Some("same".to_owned()), Some("identical".to_owned())],
accepted
);
}
#[test]
fn a_no_match_outcome_accepts_nothing() {
let outcome = TranslateOutcome::NoMatch {
message: Some("no map".to_owned()),
};
assert_eq!(0, outcome.accepted().count());
}
#[test]
fn an_invalid_outcome_is_not_valid() {
let outcome = ValidateOutcome::Invalid {
message: Some("not in the value set".to_owned()),
outcome: None,
};
assert!(!outcome.is_valid());
}
#[test]
fn a_request_names_its_operation() {
let lookup = Request::Lookup {
system: "http://example.org/cs".to_owned(),
code: "a".to_owned(),
version: None,
};
assert_eq!("CodeSystem/$lookup", lookup.operation());
}
}