use std::marker::PhantomData;
use super::super::{misc::escape_search_value, search::SearchParameter};
use crate::version::FhirVersion;
#[derive(Debug, Clone)]
pub struct NumberSearch<'a, V> {
name: &'a str,
values: Vec<String>,
_version: PhantomData<V>,
}
impl<'a, V: FhirVersion> NumberSearch<'a, V> {
#[must_use]
pub const fn new(name: &'a str) -> Self {
Self { name, values: Vec::new(), _version: PhantomData }
}
#[must_use]
pub fn or<T: ToString>(mut self, comparator: Option<V::SearchComparator>, value: T) -> Self {
let value = escape_search_value(&value.to_string());
if let Some(comparator) = comparator {
self.values.push(format!("{comparator}{value}"));
} else {
self.values.push(value);
}
self
}
}
impl<V> SearchParameter for NumberSearch<'_, V> {
fn into_query(self) -> (String, String) {
(self.name.to_owned(), self.values.join(","))
}
}
#[derive(Debug, Clone, Copy)]
pub struct DateSearch<'a, V: FhirVersion> {
pub name: &'a str,
pub comparator: Option<V::SearchComparator>,
pub value: &'a str,
}
impl<V: FhirVersion> SearchParameter for DateSearch<'_, V> {
fn into_query(self) -> (String, String) {
if let Some(comparator) = self.comparator {
(
self.name.to_owned(),
format!("{}{}", comparator.as_ref(), escape_search_value(self.value)),
)
} else {
(self.name.to_owned(), escape_search_value(self.value))
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum StringSearch<'a> {
Standard {
name: &'a str,
value: &'a str,
},
Contains {
name: &'a str,
value: &'a str,
},
Exact {
name: &'a str,
value: &'a str,
},
}
impl SearchParameter for StringSearch<'_> {
fn into_query(self) -> (String, String) {
let (name, modifier, value) = match self {
Self::Standard { name, value } => (name, "", value),
Self::Contains { name, value } => (name, ":contains", value),
Self::Exact { name, value } => (name, ":exact", value),
};
(format!("{name}{modifier}"), escape_search_value(value))
}
}
#[derive(Debug, Clone, Copy)]
pub enum TokenSearch<'a> {
Standard {
name: &'a str,
system: Option<&'a str>,
code: Option<&'a str>,
not: bool,
},
OfType {
type_system: Option<&'a str>,
type_code: Option<&'a str>,
value: Option<&'a str>,
},
In {
name: &'a str,
value_set: &'a str,
not: bool,
},
CodeText {
name: &'a str,
text: &'a str,
},
}
impl SearchParameter for TokenSearch<'_> {
fn into_query(self) -> (String, String) {
match self {
Self::Standard { name, system, code, not } => {
let key = if not { format!("{name}:not") } else { name.to_owned() };
let value = if let Some(system) = system {
format!(
"{}|{}",
escape_search_value(system),
escape_search_value(code.unwrap_or_default())
)
} else {
escape_search_value(code.unwrap_or_default())
};
(key, value)
}
Self::OfType { type_system, type_code, value } => (
"identifier:of-type".to_owned(),
format!(
"{}|{}|{}",
escape_search_value(type_system.unwrap_or_default()),
escape_search_value(type_code.unwrap_or_default()),
escape_search_value(value.unwrap_or_default())
),
),
Self::In { name, value_set, not } => {
if not {
(format!("{name}:not-in"), escape_search_value(value_set))
} else {
(format!("{name}:in"), escape_search_value(value_set))
}
}
Self::CodeText { name, text } => {
(format!("{name}:code-text"), escape_search_value(text))
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum ReferenceSearch<'a, V: FhirVersion> {
Standard {
name: &'a str,
resource_type: V::ResourceType,
id: &'a str,
version_id: Option<&'a str>,
},
Url {
name: &'a str,
url: &'a str,
version_id: Option<&'a str>,
},
Identifier {
name: &'a str,
system: Option<&'a str>,
value: Option<&'a str>,
},
Chaining {
name: &'a str,
resource_type: Option<V::ResourceType>,
target_name: &'a str,
value: &'a str,
},
}
impl<V: FhirVersion> SearchParameter for ReferenceSearch<'_, V> {
fn into_query(self) -> (String, String) {
match self {
Self::Standard { name, resource_type, id, version_id } => {
let value = if let Some(version_id) = version_id {
escape_search_value(&format!("{resource_type}/{id}/_history/{version_id}"))
} else {
escape_search_value(&format!("{resource_type}/{id}"))
};
(name.to_owned(), value)
}
Self::Url { name, url, version_id } => {
let value = if let Some(version_id) = version_id {
format!("{}|{}", escape_search_value(url), escape_search_value(version_id))
} else {
escape_search_value(url)
};
(name.to_owned(), value)
}
Self::Identifier { name, system, value } => (
name.to_owned(),
format!(
"{}|{}",
escape_search_value(system.unwrap_or_default()),
escape_search_value(value.unwrap_or_default()),
),
),
Self::Chaining { name, resource_type, target_name, value } => {
let key = if let Some(resource_type) = resource_type {
format!("{name}:{resource_type}.{target_name}")
} else {
format!("{name}.{target_name}")
};
(key, value.to_owned())
}
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct QuantitySearch<'a, V: FhirVersion> {
pub name: &'a str,
pub comparator: Option<V::SearchComparator>,
pub value: &'a str,
pub system: Option<&'a str>,
pub code: Option<&'a str>,
}
impl<V: FhirVersion> SearchParameter for QuantitySearch<'_, V> {
fn into_query(self) -> (String, String) {
let value = if let Some(comparator) = self.comparator {
format!("{comparator}{}", escape_search_value(self.value))
} else {
escape_search_value(self.value)
};
let query_value = if self.system.is_some() || self.code.is_some() {
format!(
"{value}|{}|{}",
escape_search_value(self.system.unwrap_or_default()),
escape_search_value(self.code.unwrap_or_default())
)
} else {
value
};
(self.name.to_owned(), query_value)
}
}
#[derive(Debug, Clone, Copy)]
pub enum UriSearch<'a> {
Standard {
name: &'a str,
uri: &'a str,
},
Below {
name: &'a str,
uri: &'a str,
},
Above {
name: &'a str,
uri: &'a str,
},
}
impl SearchParameter for UriSearch<'_> {
fn into_query(self) -> (String, String) {
let (name, modifier, uri) = match self {
Self::Standard { name, uri } => (name, "", uri),
Self::Below { name, uri } => (name, ":below", uri),
Self::Above { name, uri } => (name, ":above", uri),
};
(format!("{name}{modifier}"), escape_search_value(uri))
}
}
#[derive(Debug, Clone, Copy)]
pub struct MissingSearch<'a> {
pub name: &'a str,
pub missing: bool,
}
impl SearchParameter for MissingSearch<'_> {
fn into_query(self) -> (String, String) {
(format!("{}:missing", self.name), self.missing.to_string())
}
}
#[cfg(test)]
mod tests {
use fhir_model::for_all_versions;
use super::*;
use crate::version::fhir_version;
macro_rules! make_tests {
($version:ident) => {
mod $version {
use $crate::$version::{codes::SearchComparator, resources::ResourceType};
use super::*;
#[test]
fn number() {
let number = NumberSearch::<fhir_version!($version)>::new("value-quantity")
.or(Some(SearchComparator::Lt), 60)
.or(Some(SearchComparator::Gt), 100);
assert_eq!(
number.into_query(),
("value-quantity".to_owned(), "lt60,gt100".to_owned())
);
}
#[test]
fn token() {
let token = TokenSearch::Standard {
name: "identifier",
system: None,
code: Some("code"),
not: true,
};
assert_eq!(
token.into_query(),
("identifier:not".to_owned(), "code".to_owned())
);
let token = TokenSearch::Standard {
name: "identifier",
system: Some(""),
code: Some("code"),
not: false,
};
assert_eq!(token.into_query(), ("identifier".to_owned(), "|code".to_owned()));
let token = TokenSearch::Standard {
name: "identifier",
system: Some("system"),
code: None,
not: false,
};
assert_eq!(token.into_query(), ("identifier".to_owned(), "system|".to_owned()));
let token = TokenSearch::OfType {
type_system: None,
type_code: None,
value: Some("value"),
};
assert_eq!(
token.into_query(),
("identifier:of-type".to_owned(), "||value".to_owned())
);
}
#[test]
fn reference() {
let reference: ReferenceSearch<'_, fhir_version!($version)> =
ReferenceSearch::Chaining {
name: "focus",
resource_type: Some(ResourceType::Encounter),
target_name: "status",
value: "in-progress",
};
assert_eq!(
reference.into_query(),
("focus:Encounter.status".to_owned(), "in-progress".to_owned())
);
}
#[test]
fn quantity() {
let quantity = QuantitySearch::<fhir_version!($version)> {
name: "test",
comparator: None,
value: "1.0",
system: None,
code: None,
};
assert_eq!(quantity.into_query(), ("test".to_owned(), "1.0".to_owned()));
let quantity = QuantitySearch::<fhir_version!($version)> {
name: "test",
comparator: None,
value: "1.0",
system: None,
code: Some("g"),
};
assert_eq!(quantity.into_query(), ("test".to_owned(), "1.0||g".to_owned()));
}
#[test]
fn missing() {
let missing = MissingSearch { name: "identifier", missing: true };
assert_eq!(
missing.into_query(),
("identifier:missing".to_owned(), "true".to_owned())
);
}
}
};
}
for_all_versions!(make_tests);
}