1use derivative::Derivative;
2use haste_fhir_operation_error::derive::OperationOutcomeError;
3use haste_reflect::{MetaValue, derive::Reflect};
4use std::{borrow::Cow, collections::HashMap, fmt::Display};
5
6static RESULT_PARAMETERS: &[&str] = &[
7 "_count",
8 "_offset",
9 "_total",
10 "_sort",
11 "_include",
12 "_revinclude",
13 "_summary",
14 "_elements",
15 "_contained",
16 "_containedType",
17 "_since",
18 "_type",
19 "_format",
20 "_pretty",
21];
22
23#[derive(Debug, Clone, PartialEq, Eq, Reflect)]
25#[fhir_type = "BackboneElement"]
26pub struct ChainLink {
27 pub name: String,
28 pub modifier: Option<String>,
29}
30
31impl ChainLink {
32 fn parse(segment: &str) -> Result<Self, ParseError> {
35 let (name, modifier) = match segment.split_once(':') {
36 Some((name, modifier)) => (name, Some(modifier)),
37 None => (segment, None),
38 };
39
40 if name.is_empty() || modifier.is_some_and(|m| m.is_empty() || m.contains(':')) {
41 return Err(ParseError::InvalidParameter(segment.to_string()));
42 }
43
44 Ok(ChainLink {
45 name: name.to_string(),
46 modifier: modifier.map(str::to_string),
47 })
48 }
49}
50
51#[derive(Derivative, Clone, Reflect)]
57#[fhir_type = "BackboneElement"]
58#[derivative(Debug)]
59pub struct Parameter {
60 pub name: String,
61 #[derivative(Debug(format_with = "crate::redact"))]
67 pub value: Vec<String>,
68 pub modifier: Option<String>,
69 pub chains: Vec<ChainLink>,
70}
71
72fn has_escape(value: &str) -> bool {
75 value.contains('\\')
76}
77
78fn unescape(value: &str) -> Cow<'_, str> {
82 if !has_escape(value) {
83 return Cow::Borrowed(value);
84 }
85 let mut out = String::with_capacity(value.len());
86 let mut chars = value.chars();
87 while let Some(c) = chars.next() {
88 if c != '\\' {
89 out.push(c);
90 } else if let Some(next) = chars.next() {
91 if !matches!(next, ',' | '|' | '$' | '\\') {
92 out.push('\\');
93 }
94 out.push(next);
95 } else {
96 out.push(c);
97 }
98 }
99 Cow::Owned(out)
100}
101
102#[must_use]
106pub fn escape(literal: &str) -> Cow<'_, str> {
107 if !literal.contains([',', '|', '$', '\\']) {
108 return Cow::Borrowed(literal);
109 }
110 let mut out = String::with_capacity(literal.len() + 2);
111 for c in literal.chars() {
112 if matches!(c, ',' | '|' | '$' | '\\') {
113 out.push('\\');
114 }
115 out.push(c);
116 }
117 Cow::Owned(out)
118}
119
120pub struct Segments<'a> {
126 source: &'a str,
127 rest: Option<&'a str>,
128 separator: char,
129 escaped: bool,
130}
131
132impl<'a> Segments<'a> {
133 #[must_use]
135 pub fn of(value: &'a str, separator: char) -> Self {
136 Segments {
137 source: value,
138 rest: Some(value),
139 separator,
140 escaped: has_escape(value),
141 }
142 }
143
144 #[must_use]
146 pub fn source(&self) -> &'a str {
147 self.source
148 }
149
150 fn raw(mut self) -> impl Iterator<Item = &'a str> {
152 std::iter::from_fn(move || self.next_raw())
153 }
154
155 fn next_raw(&mut self) -> Option<&'a str> {
158 let current = self.rest?;
159 let split = if self.escaped {
160 let mut escaped = false;
161 current.char_indices().find_map(|(i, c)| {
162 if escaped {
163 escaped = false;
164 None
165 } else if c == '\\' {
166 escaped = true;
167 None
168 } else {
169 (c == self.separator).then_some(i)
170 }
171 })
172 } else {
173 current.find(self.separator)
174 };
175 if let Some(i) = split {
176 self.rest = Some(¤t[i + self.separator.len_utf8()..]);
177 Some(¤t[..i])
178 } else {
179 self.rest = None;
180 Some(current)
181 }
182 }
183}
184
185impl<'a> Iterator for Segments<'a> {
186 type Item = Cow<'a, str>;
187
188 fn next(&mut self) -> Option<Self::Item> {
189 let raw = self.next_raw()?;
190 Some(if self.escaped {
191 unescape(raw)
192 } else {
193 Cow::Borrowed(raw)
194 })
195 }
196}
197
198impl Parameter {
199 #[must_use]
201 pub fn is_chained(&self) -> bool {
202 !self.chains.is_empty()
203 }
204
205 #[must_use]
207 pub fn key(&self) -> String {
208 let segments = std::iter::once((&self.name, &self.modifier))
209 .chain(self.chains.iter().map(|link| (&link.name, &link.modifier)));
210
211 let mut key = String::new();
212 for (i, (name, modifier)) in segments.enumerate() {
213 if i > 0 {
214 key.push('.');
215 }
216 key.push_str(name);
217 if let Some(modifier) = modifier {
218 key.push(':');
219 key.push_str(modifier);
220 }
221 }
222 key
223 }
224
225 #[must_use]
228 pub fn value_string(&self) -> String {
229 self.value.join(",")
230 }
231
232 #[must_use]
235 pub fn values(&self) -> Cow<'_, [String]> {
236 if self.value.iter().any(|value| has_escape(value)) {
237 Cow::Owned(
238 self.value
239 .iter()
240 .map(|value| unescape(value).into_owned())
241 .collect(),
242 )
243 } else {
244 Cow::Borrowed(&self.value)
245 }
246 }
247
248 pub fn each_value(&self) -> impl Iterator<Item = Cow<'_, str>> {
251 self.value.iter().map(|value| unescape(value))
252 }
253
254 pub fn segmented(&self, separator: char) -> impl Iterator<Item = Segments<'_>> {
257 self.value
258 .iter()
259 .map(move |value| Segments::of(value, separator))
260 }
261
262 fn parse(key: &str, value: &str) -> Result<Self, ParseError> {
263 let mut segments = key.split('.').map(ChainLink::parse);
264 let first = segments
266 .next()
267 .ok_or_else(|| ParseError::InvalidParameter(key.to_string()))??;
268
269 Ok(Parameter {
270 name: first.name,
271 modifier: first.modifier,
272 value: Segments::of(value, ',').raw().map(str::to_string).collect(),
275 chains: segments.collect::<Result<_, _>>()?,
276 })
277 }
278}
279
280impl From<(String, Vec<String>)> for Parameter {
281 fn from(tuple: (String, Vec<String>)) -> Self {
282 Parameter {
283 name: tuple.0,
284 value: tuple.1,
285 modifier: None,
286 chains: Vec::new(),
287 }
288 }
289}
290
291impl From<(String, Vec<String>, String)> for Parameter {
292 fn from(tuple: (String, Vec<String>, String)) -> Self {
293 Parameter {
294 name: tuple.0,
295 value: tuple.1,
296 modifier: Some(tuple.2),
297 chains: Vec::new(),
298 }
299 }
300}
301
302#[derive(Derivative, Clone)]
305#[derivative(Debug = "transparent")]
306pub enum ParsedParameter {
307 #[derivative(Debug = "transparent")]
308 Result(Parameter),
309 #[derivative(Debug = "transparent")]
310 Resource(Parameter),
311}
312
313impl<T: Into<Parameter>> From<T> for ParsedParameter {
314 fn from(param: T) -> Self {
315 let param = param.into();
316 if RESULT_PARAMETERS.contains(¶m.name.as_str()) {
317 ParsedParameter::Result(param)
318 } else {
319 ParsedParameter::Resource(param)
320 }
321 }
322}
323
324impl ParsedParameter {
325 #[must_use]
326 pub fn name(&self) -> &str {
327 match self {
328 ParsedParameter::Result(p) | ParsedParameter::Resource(p) => &p.name,
329 }
330 }
331}
332
333impl MetaValue for ParsedParameter {
334 fn fields(&self) -> Vec<&'static str> {
335 match self {
336 ParsedParameter::Result(p) | ParsedParameter::Resource(p) => p.fields(),
337 }
338 }
339
340 fn get_field<'a>(&'a self, field: &str) -> Option<&'a dyn MetaValue> {
341 match self {
342 ParsedParameter::Result(p) | ParsedParameter::Resource(p) => p.get_field(field),
343 }
344 }
345
346 fn get_field_mut<'a>(&'a mut self, field: &str) -> Option<&'a mut dyn MetaValue> {
347 match self {
348 ParsedParameter::Result(p) | ParsedParameter::Resource(p) => p.get_field_mut(field),
349 }
350 }
351
352 fn get_index(&self, index: usize) -> Option<&dyn MetaValue> {
353 match self {
354 ParsedParameter::Result(p) | ParsedParameter::Resource(p) => p.get_index(index),
355 }
356 }
357
358 fn get_index_mut(&mut self, index: usize) -> Option<&mut dyn MetaValue> {
359 match self {
360 ParsedParameter::Result(p) | ParsedParameter::Resource(p) => p.get_index_mut(index),
361 }
362 }
363
364 fn flatten(&self) -> Vec<&dyn MetaValue> {
365 match self {
366 ParsedParameter::Result(p) | ParsedParameter::Resource(p) => p.flatten(),
367 }
368 }
369
370 fn as_any(&self) -> &dyn std::any::Any {
371 match self {
372 ParsedParameter::Result(p) | ParsedParameter::Resource(p) => p.as_any(),
373 }
374 }
375
376 fn fhir_type(&self) -> &'static str {
377 match self {
378 ParsedParameter::Result(p) | ParsedParameter::Resource(p) => p.fhir_type(),
379 }
380 }
381
382 fn is_many(&self) -> bool {
383 false
384 }
385}
386
387#[derive(Debug, OperationOutcomeError)]
388pub enum ParseError {
389 #[fatal(
390 code = "invalid",
391 diagnostic = "Error parsing query parameters '{arg0}'"
392 )]
393 InvalidParameter(String),
394}
395
396impl Display for ParseError {
397 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
398 match self {
399 ParseError::InvalidParameter(param) => {
400 write!(f, "Invalid query parameter: {param}")
401 }
402 }
403 }
404}
405
406impl std::error::Error for ParseError {}
407
408#[derive(Derivative, Clone)]
409#[derivative(Debug = "transparent")]
410pub struct ParsedParameters(Vec<ParsedParameter>);
411
412impl<T: Into<ParsedParameter>> From<Vec<T>> for ParsedParameters {
413 fn from(params: Vec<T>) -> Self {
414 let parsed_params = params.into_iter().map(std::convert::Into::into).collect();
415 ParsedParameters(parsed_params)
416 }
417}
418
419impl ParsedParameters {
420 #[must_use]
421 pub fn new(params: Vec<ParsedParameter>) -> Self {
422 Self(params)
423 }
424 #[must_use]
425 pub fn parameters(&self) -> &Vec<ParsedParameter> {
426 &self.0
427 }
428 #[must_use]
429 pub fn owned_parameters(self) -> Vec<ParsedParameter> {
430 self.0
431 }
432 #[must_use]
433 pub fn get(&self, name: &str) -> Option<&ParsedParameter> {
434 self.0.iter().find(|p| match p {
435 ParsedParameter::Resource(param) | ParsedParameter::Result(param) => param.name == name,
436 })
437 }
438}
439
440impl MetaValue for ParsedParameters {
441 fn fields(&self) -> Vec<&'static str> {
442 todo!()
443 }
444
445 fn get_field<'a>(&'a self, field: &str) -> Option<&'a dyn MetaValue> {
446 if let Some(p) = self.get(field) {
447 Some(p)
448 } else {
449 None
450 }
451 }
452
453 fn get_field_mut<'a>(&'a mut self, _field: &str) -> Option<&'a mut dyn MetaValue> {
454 None
455 }
456
457 fn get_index(&self, _index: usize) -> Option<&dyn MetaValue> {
458 None
459 }
460
461 fn get_index_mut(&mut self, _index: usize) -> Option<&mut dyn MetaValue> {
462 None
463 }
464
465 fn flatten(&self) -> Vec<&dyn MetaValue> {
466 vec![]
467 }
468
469 fn as_any(&self) -> &dyn std::any::Any {
470 self
471 }
472
473 fn fhir_type(&self) -> &'static str {
474 "BackboneElement"
475 }
476
477 fn is_many(&self) -> bool {
478 true
479 }
480}
481
482impl TryFrom<&str> for ParsedParameters {
483 type Error = ParseError;
484 fn try_from(query_string: &str) -> Result<Self, ParseError> {
485 let mut query_string = query_string;
486 if query_string.is_empty() {
487 return Ok(Self(vec![]));
488 }
489
490 if query_string.starts_with('?') {
491 query_string = &query_string[1..];
492 }
493
494 let pairs = query_string
495 .split('&')
496 .map(|pair| {
497 pair.split_once('=')
498 .ok_or_else(|| ParseError::InvalidParameter(pair.to_string()))
499 })
500 .collect::<Result<Vec<(&str, &str)>, ParseError>>()?;
501
502 Self::try_from(pairs.as_slice())
503 }
504}
505
506impl TryFrom<&HashMap<String, String>> for ParsedParameters {
507 type Error = ParseError;
508 fn try_from(query_params: &HashMap<String, String>) -> Result<Self, ParseError> {
509 let pairs: Vec<(&String, &String)> = query_params.iter().collect();
510
511 Self::try_from(pairs.as_slice())
512 }
513}
514
515impl<Name: AsRef<str>, Value: AsRef<str>> TryFrom<&[(Name, Value)]> for ParsedParameters {
519 type Error = ParseError;
520 fn try_from(pairs: &[(Name, Value)]) -> Result<Self, ParseError> {
521 let params = pairs
522 .iter()
523 .map(|(name, value)| {
524 Parameter::parse(name.as_ref(), value.as_ref()).map(ParsedParameter::from)
525 })
526 .collect::<Result<Vec<ParsedParameter>, ParseError>>()?;
527
528 Ok(Self(params))
529 }
530}
531
532#[derive(Debug, Clone, Copy, PartialEq, Eq)]
535pub enum Prefix {
536 Eq,
537 Ne,
538 Gt,
539 Lt,
540 Ge,
541 Le,
542 Sa,
543 Eb,
544 Ap,
545}
546
547impl Prefix {
548 #[must_use]
550 pub fn parse(code: &str) -> Option<Self> {
551 Some(match code {
552 "eq" => Prefix::Eq,
553 "ne" => Prefix::Ne,
554 "gt" => Prefix::Gt,
555 "lt" => Prefix::Lt,
556 "ge" => Prefix::Ge,
557 "le" => Prefix::Le,
558 "sa" => Prefix::Sa,
559 "eb" => Prefix::Eb,
560 "ap" => Prefix::Ap,
561 _ => return None,
562 })
563 }
564}
565
566#[must_use]
569pub fn parse_prefix(value: &str) -> (Prefix, &str) {
570 match (value.get(..2).and_then(Prefix::parse), value.get(2..)) {
571 (Some(prefix), Some(rest)) if !rest.is_empty() => (prefix, rest),
572 _ => (Prefix::Eq, value),
573 }
574}
575
576#[cfg(test)]
577mod tests {
578 use super::*;
579
580 #[test]
581 fn test_parse_prefix() {
582 let cases = vec![
583 ("lt5.0", (Prefix::Lt, "5.0")),
584 ("le10", (Prefix::Le, "10")),
585 ("gt3.14", (Prefix::Gt, "3.14")),
586 ("ge2.71", (Prefix::Ge, "2.71")),
587 ("eq42", (Prefix::Eq, "42")),
588 ("ne0", (Prefix::Ne, "0")),
589 ("sa2020", (Prefix::Sa, "2020")),
590 ("eb2020-01", (Prefix::Eb, "2020-01")),
591 ("ap70", (Prefix::Ap, "70")),
592 ("ne", (Prefix::Eq, "ne")),
593 ("é5", (Prefix::Eq, "é5")),
594 ("5.0", (Prefix::Eq, "5.0")),
595 ("10", (Prefix::Eq, "10")),
596 ];
597
598 for (input, expected) in cases {
599 let result = parse_prefix(input);
600 assert_eq!(result, expected, "Failed for input: {input}");
601 }
602 }
603
604 #[test]
605 fn test_split_unescaped() {
606 let split = |v| Segments::of(v, ',').raw().collect::<Vec<_>>();
607 assert_eq!(split("a,b"), ["a", "b"]);
608 assert_eq!(split(r"a\,b"), [r"a\,b"]);
609 assert_eq!(split(r"a\\,b"), [r"a\\", "b"]);
610 assert_eq!(split(""), [""]);
611 assert_eq!(split("é,ü"), ["é", "ü"]);
612 }
613
614 #[test]
615 fn test_unescape() {
616 assert_eq!(unescape(r"a\,b\|c\$d\\e"), r"a,b|c$d\e");
617 assert_eq!(unescape(r"\x"), r"\x");
618 assert_eq!(unescape("plain"), "plain");
619 assert_eq!(unescape("trailing\\"), "trailing\\");
620 assert!(matches!(unescape("plain"), Cow::Borrowed(_)));
621 }
622
623 #[test]
624 fn test_escape_round_trips() {
625 for literal in ["plain", "a,b|c$d\\e", r"\x", "é|ü"] {
626 assert_eq!(unescape(&escape(literal)), literal);
627 }
628 assert!(matches!(escape("plain"), Cow::Borrowed(_)));
629 assert_eq!(escape(r"a\,b"), r"a\\\,b");
630 }
631
632 #[test]
633 fn test_segments() {
634 let segments = |v| Segments::of(v, '|').collect::<Vec<_>>();
635 assert_eq!(segments("a|b"), ["a", "b"]);
636 assert_eq!(segments(r"a\|b|c"), ["a|b", "c"]);
637 assert_eq!(segments(r"x\\|y\|z"), [r"x\", "y|z"]);
638 assert_eq!(segments("|"), ["", ""]);
639 assert_eq!(segments(""), [""]);
640 assert_eq!(segments("é|ü"), ["é", "ü"]);
641 assert!(Segments::of("a|b", '|').all(|segment| matches!(segment, Cow::Borrowed(_))));
643 assert_eq!(Segments::of("a|b", '|').source(), "a|b");
644 }
645
646 #[test]
647 fn test_parse_escaped_values() {
648 let parameter = |q| {
649 let params = ParsedParameters::try_from(q).unwrap();
650 let ParsedParameter::Resource(p) = params.get("p").unwrap() else {
651 panic!("expected a resource parameter")
652 };
653 p.clone()
654 };
655 assert_eq!(parameter(r"p=Smith\,Jones").value, [r"Smith\,Jones"]);
657 assert_eq!(parameter("p=Smith,Jones").value, ["Smith", "Jones"]);
658 assert_eq!(
659 parameter(r"p=http://a\|b|1,x").value,
660 [r"http://a\|b|1", "x"]
661 );
662 assert_eq!(parameter(r"p=a\\,b").value, [r"a\\", "b"]);
663
664 assert_eq!(&*parameter(r"p=Smith\,Jones").values(), ["Smith,Jones"]);
666 assert_eq!(&*parameter(r"p=a\\,b").values(), [r"a\", "b"]);
667 assert!(matches!(
668 parameter("p=Smith,Jones").values(),
669 Cow::Borrowed(_)
670 ));
671 assert_eq!(
672 parameter(r"p=a\,b,c").each_value().collect::<Vec<_>>(),
673 ["a,b", "c"]
674 );
675 assert_eq!(
676 parameter(r"p=http://a\|b|1,x")
677 .segmented('|')
678 .map(Iterator::collect::<Vec<_>>)
679 .collect::<Vec<_>>(),
680 [vec!["http://a|b", "1"], vec!["x"]]
681 );
682 }
683
684 #[test]
685 fn test_value_string_round_trips() {
686 for query in [
687 r"p=Smith\,Jones",
688 "p=Smith,Jones",
689 r"p=a\|b|1,x\,y",
690 r"p=a\\,b",
691 ] {
692 let params = ParsedParameters::try_from(query).unwrap();
693 let ParsedParameter::Resource(p) = params.get("p").unwrap() else {
694 panic!("expected a resource parameter")
695 };
696 assert_eq!(format!("p={}", p.value_string()), query);
697 }
698 }
699
700 #[test]
701 fn test_parse_parameters() {
702 let query_string = "?name=John,Doe&_count=10&address.city=NewYork&status:exact=active";
703 let parsed_params = ParsedParameters::try_from(query_string).unwrap();
704
705 assert_eq!(parsed_params.parameters().len(), 4);
706
707 match parsed_params.get("name") {
708 Some(ParsedParameter::Resource(param)) => {
709 assert_eq!(param.name, "name");
710 assert_eq!(param.value, vec!["John", "Doe"]);
711 assert!(param.modifier.is_none());
712 assert!(param.chains.is_empty());
713 }
714 _ => panic!("Expected Resource parameter"),
715 }
716
717 match parsed_params.get("_count") {
718 Some(ParsedParameter::Result(param)) => {
719 assert_eq!(param.name, "_count");
720 assert_eq!(param.value, vec!["10"]);
721 assert!(param.modifier.is_none());
722 assert!(param.chains.is_empty());
723 }
724 _ => panic!("Expected Result parameter"),
725 }
726
727 match parsed_params.get("address") {
728 Some(ParsedParameter::Resource(param)) => {
729 assert_eq!(param.name, "address");
730 assert_eq!(param.value, vec!["NewYork"]);
731 assert!(param.modifier.is_none());
732 assert_eq!(
733 param.chains,
734 vec![ChainLink {
735 name: "city".to_string(),
736 modifier: None
737 }]
738 );
739 }
740 _ => panic!("Expected Resource parameter"),
741 }
742
743 match parsed_params.get("status") {
744 Some(ParsedParameter::Resource(param)) => {
745 assert_eq!(param.name, "status");
746 assert_eq!(param.value, vec!["active"]);
747 assert_eq!(param.modifier, Some("exact".to_string()));
748 assert!(param.chains.is_empty());
749 }
750 _ => panic!("Expected Resource parameter"),
751 }
752 }
753}