1use ::std::marker::PhantomData;
4
5use super::super::{misc::escape_search_value, search::SearchParameter};
6use crate::version::FhirVersion;
7
8#[derive(Debug, Clone)]
13pub struct NumberSearch<'a, V> {
14 name: &'a str,
16 values: Vec<String>,
19 _version: PhantomData<V>,
21}
22
23impl<'a, V: FhirVersion> NumberSearch<'a, V> {
24 #[must_use]
26 pub const fn new(name: &'a str) -> Self {
27 Self { name, values: Vec::new(), _version: PhantomData }
28 }
29
30 #[must_use]
32 pub fn or<T: ToString>(mut self, comparator: Option<V::SearchComparator>, value: T) -> Self {
33 let value = escape_search_value(&value.to_string());
34 if let Some(comparator) = comparator {
35 self.values.push(format!("{comparator}{value}"));
36 } else {
37 self.values.push(value);
38 }
39 self
40 }
41}
42
43impl<V> SearchParameter for NumberSearch<'_, V> {
44 fn into_query(self) -> (String, String) {
45 (self.name.to_owned(), self.values.join(","))
46 }
47}
48
49#[derive(Debug, Clone, Copy)]
54pub struct DateSearch<'a, V: FhirVersion> {
55 pub name: &'a str,
57 pub comparator: Option<V::SearchComparator>,
59 pub value: &'a str,
61}
62
63impl<V: FhirVersion> SearchParameter for DateSearch<'_, V> {
64 fn into_query(self) -> (String, String) {
65 if let Some(comparator) = self.comparator {
66 (
67 self.name.to_owned(),
68 format!("{}{}", comparator.as_ref(), escape_search_value(self.value)),
69 )
70 } else {
71 (self.name.to_owned(), escape_search_value(self.value))
72 }
73 }
74}
75
76#[derive(Debug, Clone, Copy)]
81pub enum StringSearch<'a> {
82 Standard {
84 name: &'a str,
86 value: &'a str,
88 },
89 Contains {
91 name: &'a str,
93 value: &'a str,
95 },
96 Exact {
99 name: &'a str,
101 value: &'a str,
103 },
104}
105
106impl SearchParameter for StringSearch<'_> {
107 fn into_query(self) -> (String, String) {
108 let (name, modifier, value) = match self {
109 Self::Standard { name, value } => (name, "", value),
110 Self::Contains { name, value } => (name, ":contains", value),
111 Self::Exact { name, value } => (name, ":exact", value),
112 };
113 (format!("{name}{modifier}"), escape_search_value(value))
114 }
115}
116
117#[derive(Debug, Clone, Copy)]
122pub enum TokenSearch<'a> {
123 Standard {
125 name: &'a str,
127 system: Option<&'a str>,
130 code: Option<&'a str>,
132 not: bool,
134 },
135 OfType {
138 type_system: Option<&'a str>,
140 type_code: Option<&'a str>,
142 value: Option<&'a str>,
144 },
145 In {
147 name: &'a str,
149 value_set: &'a str,
151 not: bool,
153 },
154 CodeText {
156 name: &'a str,
158 text: &'a str,
160 },
161}
162
163impl SearchParameter for TokenSearch<'_> {
164 fn into_query(self) -> (String, String) {
165 match self {
166 Self::Standard { name, system, code, not } => {
167 let key = if not { format!("{name}:not") } else { name.to_owned() };
168 let value = if let Some(system) = system {
169 format!(
170 "{}|{}",
171 escape_search_value(system),
172 escape_search_value(code.unwrap_or_default())
173 )
174 } else {
175 escape_search_value(code.unwrap_or_default())
176 };
177 (key, value)
178 }
179 Self::OfType { type_system, type_code, value } => (
180 "identifier:of-type".to_owned(),
181 format!(
182 "{}|{}|{}",
183 escape_search_value(type_system.unwrap_or_default()),
184 escape_search_value(type_code.unwrap_or_default()),
185 escape_search_value(value.unwrap_or_default())
186 ),
187 ),
188 Self::In { name, value_set, not } => {
189 if not {
190 (format!("{name}:not-in"), escape_search_value(value_set))
191 } else {
192 (format!("{name}:in"), escape_search_value(value_set))
193 }
194 }
195 Self::CodeText { name, text } => {
196 (format!("{name}:code-text"), escape_search_value(text))
197 }
198 }
199 }
200}
201
202#[derive(Debug, Clone, Copy)]
208pub enum ReferenceSearch<'a, V: FhirVersion> {
209 Standard {
211 name: &'a str,
213 resource_type: V::ResourceType,
215 id: &'a str,
217 version_id: Option<&'a str>,
219 },
220 Url {
222 name: &'a str,
224 url: &'a str,
226 version_id: Option<&'a str>,
228 },
229 Identifier {
231 name: &'a str,
233 system: Option<&'a str>,
235 value: Option<&'a str>,
237 },
238 Chaining {
241 name: &'a str,
243 resource_type: Option<V::ResourceType>,
245 target_name: &'a str,
247 value: &'a str,
250 },
251}
252
253impl<V: FhirVersion> SearchParameter for ReferenceSearch<'_, V> {
254 fn into_query(self) -> (String, String) {
255 match self {
256 Self::Standard { name, resource_type, id, version_id } => {
257 let value = if let Some(version_id) = version_id {
258 escape_search_value(&format!("{resource_type}/{id}/_history/{version_id}"))
259 } else {
260 escape_search_value(&format!("{resource_type}/{id}"))
261 };
262 (name.to_owned(), value)
263 }
264 Self::Url { name, url, version_id } => {
265 let value = if let Some(version_id) = version_id {
266 format!("{}|{}", escape_search_value(url), escape_search_value(version_id))
267 } else {
268 escape_search_value(url)
269 };
270 (name.to_owned(), value)
271 }
272 Self::Identifier { name, system, value } => (
273 name.to_owned(),
274 format!(
275 "{}|{}",
276 escape_search_value(system.unwrap_or_default()),
277 escape_search_value(value.unwrap_or_default()),
278 ),
279 ),
280 Self::Chaining { name, resource_type, target_name, value } => {
281 let key = if let Some(resource_type) = resource_type {
282 format!("{name}:{resource_type}.{target_name}")
283 } else {
284 format!("{name}.{target_name}")
285 };
286 (key, value.to_owned())
287 }
288 }
289 }
290}
291
292#[derive(Debug, Clone, Copy)]
297pub struct QuantitySearch<'a, V: FhirVersion> {
298 pub name: &'a str,
300 pub comparator: Option<V::SearchComparator>,
302 pub value: &'a str,
304 pub system: Option<&'a str>,
306 pub code: Option<&'a str>,
308}
309
310impl<V: FhirVersion> SearchParameter for QuantitySearch<'_, V> {
311 fn into_query(self) -> (String, String) {
312 let value = if let Some(comparator) = self.comparator {
313 format!("{comparator}{}", escape_search_value(self.value))
314 } else {
315 escape_search_value(self.value)
316 };
317
318 let query_value = if self.system.is_some() || self.code.is_some() {
319 format!(
320 "{value}|{}|{}",
321 escape_search_value(self.system.unwrap_or_default()),
322 escape_search_value(self.code.unwrap_or_default())
323 )
324 } else {
325 value
326 };
327
328 (self.name.to_owned(), query_value)
329 }
330}
331
332#[derive(Debug, Clone, Copy)]
337pub enum UriSearch<'a> {
338 Standard {
340 name: &'a str,
342 uri: &'a str,
344 },
345 Below {
348 name: &'a str,
350 uri: &'a str,
352 },
353 Above {
356 name: &'a str,
358 uri: &'a str,
360 },
361}
362
363impl SearchParameter for UriSearch<'_> {
364 fn into_query(self) -> (String, String) {
365 let (name, modifier, uri) = match self {
366 Self::Standard { name, uri } => (name, "", uri),
367 Self::Below { name, uri } => (name, ":below", uri),
368 Self::Above { name, uri } => (name, ":above", uri),
369 };
370 (format!("{name}{modifier}"), escape_search_value(uri))
371 }
372}
373
374#[derive(Debug, Clone, Copy)]
377pub struct MissingSearch<'a> {
378 pub name: &'a str,
380 pub missing: bool,
382}
383
384impl SearchParameter for MissingSearch<'_> {
385 fn into_query(self) -> (String, String) {
386 (format!("{}:missing", self.name), self.missing.to_string())
387 }
388}
389
390#[cfg(test)]
391mod tests {
392 use fhir_model::for_all_versions;
393
394 use super::*;
395 use crate::version::fhir_version;
396
397 macro_rules! make_tests {
398 ($version:ident) => {
399 mod $version {
400 use $crate::$version::{codes::SearchComparator, resources::ResourceType};
401
402 use super::*;
403
404 #[test]
405 fn number() {
406 let number = NumberSearch::<fhir_version!($version)>::new("value-quantity")
407 .or(Some(SearchComparator::Lt), 60)
408 .or(Some(SearchComparator::Gt), 100);
409 assert_eq!(
410 number.into_query(),
411 ("value-quantity".to_owned(), "lt60,gt100".to_owned())
412 );
413 }
414
415 #[test]
416 fn token() {
417 let token = TokenSearch::Standard {
418 name: "identifier",
419 system: None,
420 code: Some("code"),
421 not: true,
422 };
423 assert_eq!(
424 token.into_query(),
425 ("identifier:not".to_owned(), "code".to_owned())
426 );
427
428 let token = TokenSearch::Standard {
429 name: "identifier",
430 system: Some(""),
431 code: Some("code"),
432 not: false,
433 };
434 assert_eq!(token.into_query(), ("identifier".to_owned(), "|code".to_owned()));
435
436 let token = TokenSearch::Standard {
437 name: "identifier",
438 system: Some("system"),
439 code: None,
440 not: false,
441 };
442 assert_eq!(token.into_query(), ("identifier".to_owned(), "system|".to_owned()));
443
444 let token = TokenSearch::OfType {
445 type_system: None,
446 type_code: None,
447 value: Some("value"),
448 };
449 assert_eq!(
450 token.into_query(),
451 ("identifier:of-type".to_owned(), "||value".to_owned())
452 );
453 }
454
455 #[test]
456 fn reference() {
457 let reference: ReferenceSearch<'_, fhir_version!($version)> =
458 ReferenceSearch::Chaining {
459 name: "focus",
460 resource_type: Some(ResourceType::Encounter),
461 target_name: "status",
462 value: "in-progress",
463 };
464 assert_eq!(
465 reference.into_query(),
466 ("focus:Encounter.status".to_owned(), "in-progress".to_owned())
467 );
468 }
469
470 #[test]
471 fn quantity() {
472 let quantity = QuantitySearch::<fhir_version!($version)> {
473 name: "test",
474 comparator: None,
475 value: "1.0",
476 system: None,
477 code: None,
478 };
479 assert_eq!(quantity.into_query(), ("test".to_owned(), "1.0".to_owned()));
480
481 let quantity = QuantitySearch::<fhir_version!($version)> {
482 name: "test",
483 comparator: None,
484 value: "1.0",
485 system: None,
486 code: Some("g"),
487 };
488 assert_eq!(quantity.into_query(), ("test".to_owned(), "1.0||g".to_owned()));
489 }
490
491 #[test]
492 fn missing() {
493 let missing = MissingSearch { name: "identifier", missing: true };
494 assert_eq!(
495 missing.into_query(),
496 ("identifier:missing".to_owned(), "true".to_owned())
497 );
498 }
499 }
500 };
501 }
502 for_all_versions!(make_tests);
503}