Skip to main content

haste_fhir_client/
url.rs

1use derivative::Derivative;
2use haste_fhir_operation_error::derive::OperationOutcomeError;
3use haste_reflect::{MetaValue, derive::Reflect};
4use std::{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];
19
20#[derive(Derivative, Clone, Reflect)]
21#[fhir_type = "BackboneElement"]
22#[derivative(Debug)]
23pub struct Parameter {
24    pub name: String,
25    #[derivative(Debug(format_with = "crate::redact"))]
26    pub value: Vec<String>,
27    pub modifier: Option<String>,
28    pub chains: Option<Vec<String>>,
29}
30
31impl From<(String, Vec<String>)> for Parameter {
32    fn from(tuple: (String, Vec<String>)) -> Self {
33        Parameter {
34            name: tuple.0,
35            value: tuple.1,
36            modifier: None,
37            chains: None,
38        }
39    }
40}
41
42impl From<(String, Vec<String>, String)> for Parameter {
43    fn from(tuple: (String, Vec<String>, String)) -> Self {
44        Parameter {
45            name: tuple.0,
46            value: tuple.1,
47            modifier: Some(tuple.2),
48            chains: None,
49        }
50    }
51}
52
53impl From<(String, Vec<String>, Option<String>, Vec<String>)> for Parameter {
54    fn from(tuple: (String, Vec<String>, Option<String>, Vec<String>)) -> Self {
55        Parameter {
56            name: tuple.0,
57            value: tuple.1,
58            modifier: tuple.2,
59            chains: Some(tuple.3),
60        }
61    }
62}
63
64/// Represent both resource parameters IE Patient.name and
65/// result parameters IE _count
66#[derive(Derivative, Clone)]
67#[derivative(Debug = "transparent")]
68pub enum ParsedParameter {
69    #[derivative(Debug = "transparent")]
70    Result(Parameter),
71    #[derivative(Debug = "transparent")]
72    Resource(Parameter),
73}
74
75impl<T: Into<Parameter>> From<T> for ParsedParameter {
76    fn from(param: T) -> Self {
77        let param = param.into();
78        if RESULT_PARAMETERS.contains(&param.name.as_str()) {
79            ParsedParameter::Result(param)
80        } else {
81            ParsedParameter::Resource(param)
82        }
83    }
84}
85
86impl ParsedParameter {
87    #[must_use]
88    pub fn name(&self) -> &str {
89        match self {
90            ParsedParameter::Result(p) | ParsedParameter::Resource(p) => &p.name,
91        }
92    }
93}
94
95impl MetaValue for ParsedParameter {
96    fn fields(&self) -> Vec<&'static str> {
97        match self {
98            ParsedParameter::Result(p) | ParsedParameter::Resource(p) => p.fields(),
99        }
100    }
101
102    fn get_field<'a>(&'a self, field: &str) -> Option<&'a dyn MetaValue> {
103        match self {
104            ParsedParameter::Result(p) | ParsedParameter::Resource(p) => p.get_field(field),
105        }
106    }
107
108    fn get_field_mut<'a>(&'a mut self, field: &str) -> Option<&'a mut dyn MetaValue> {
109        match self {
110            ParsedParameter::Result(p) | ParsedParameter::Resource(p) => p.get_field_mut(field),
111        }
112    }
113
114    fn get_index(&self, index: usize) -> Option<&dyn MetaValue> {
115        match self {
116            ParsedParameter::Result(p) | ParsedParameter::Resource(p) => p.get_index(index),
117        }
118    }
119
120    fn get_index_mut(&mut self, index: usize) -> Option<&mut dyn MetaValue> {
121        match self {
122            ParsedParameter::Result(p) | ParsedParameter::Resource(p) => p.get_index_mut(index),
123        }
124    }
125
126    fn flatten(&self) -> Vec<&dyn MetaValue> {
127        match self {
128            ParsedParameter::Result(p) | ParsedParameter::Resource(p) => p.flatten(),
129        }
130    }
131
132    fn as_any(&self) -> &dyn std::any::Any {
133        match self {
134            ParsedParameter::Result(p) | ParsedParameter::Resource(p) => p.as_any(),
135        }
136    }
137
138    fn fhir_type(&self) -> &'static str {
139        match self {
140            ParsedParameter::Result(p) | ParsedParameter::Resource(p) => p.fhir_type(),
141        }
142    }
143
144    fn is_many(&self) -> bool {
145        false
146    }
147}
148
149#[derive(Debug, OperationOutcomeError)]
150pub enum ParseError {
151    #[fatal(
152        code = "invalid",
153        diagnostic = "Error parsing query parameters '{arg0}'"
154    )]
155    InvalidParameter(String),
156}
157
158impl Display for ParseError {
159    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
160        match self {
161            ParseError::InvalidParameter(param) => {
162                write!(f, "Invalid query parameter: {param}")
163            }
164        }
165    }
166}
167
168impl std::error::Error for ParseError {}
169
170#[derive(Derivative, Clone)]
171#[derivative(Debug = "transparent")]
172pub struct ParsedParameters(Vec<ParsedParameter>);
173
174impl<T: Into<ParsedParameter>> From<Vec<T>> for ParsedParameters {
175    fn from(params: Vec<T>) -> Self {
176        let parsed_params = params.into_iter().map(std::convert::Into::into).collect();
177        ParsedParameters(parsed_params)
178    }
179}
180
181impl ParsedParameters {
182    #[must_use]
183    pub fn new(params: Vec<ParsedParameter>) -> Self {
184        Self(params)
185    }
186    #[must_use]
187    pub fn parameters(&self) -> &Vec<ParsedParameter> {
188        &self.0
189    }
190    #[must_use]
191    pub fn owned_parameters(self) -> Vec<ParsedParameter> {
192        self.0
193    }
194    #[must_use]
195    pub fn get(&self, name: &str) -> Option<&ParsedParameter> {
196        self.0.iter().find(|p| match p {
197            ParsedParameter::Resource(param) | ParsedParameter::Result(param) => param.name == name,
198        })
199    }
200}
201
202impl MetaValue for ParsedParameters {
203    fn fields(&self) -> Vec<&'static str> {
204        todo!()
205    }
206
207    fn get_field<'a>(&'a self, field: &str) -> Option<&'a dyn MetaValue> {
208        if let Some(p) = self.get(field) {
209            Some(p)
210        } else {
211            None
212        }
213    }
214
215    fn get_field_mut<'a>(&'a mut self, _field: &str) -> Option<&'a mut dyn MetaValue> {
216        None
217    }
218
219    fn get_index(&self, _index: usize) -> Option<&dyn MetaValue> {
220        None
221    }
222
223    fn get_index_mut(&mut self, _index: usize) -> Option<&mut dyn MetaValue> {
224        None
225    }
226
227    fn flatten(&self) -> Vec<&dyn MetaValue> {
228        vec![]
229    }
230
231    fn as_any(&self) -> &dyn std::any::Any {
232        self
233    }
234
235    fn fhir_type(&self) -> &'static str {
236        "BackboneElement"
237    }
238
239    fn is_many(&self) -> bool {
240        true
241    }
242}
243
244impl TryFrom<&str> for ParsedParameters {
245    type Error = ParseError;
246    fn try_from(query_string: &str) -> Result<Self, ParseError> {
247        let mut query_string = query_string;
248        if query_string.is_empty() {
249            return Ok(Self(vec![]));
250        }
251
252        if query_string.starts_with('?') {
253            query_string = &query_string[1..];
254        }
255
256        let query_map = query_string
257            .split('&')
258            .map(|pair| {
259                pair.split_once('=')
260                    .map(|(key, value)| (key.to_string(), value.to_string()))
261                    .ok_or_else(|| ParseError::InvalidParameter(pair.to_string()))
262            })
263            .collect::<Result<std::collections::HashMap<String, String>, ParseError>>()?;
264
265        Self::try_from(&query_map)
266    }
267}
268
269impl TryFrom<&HashMap<String, String>> for ParsedParameters {
270    type Error = ParseError;
271    fn try_from(query_params: &HashMap<String, String>) -> Result<Self, ParseError> {
272        if query_params.is_empty() {
273            return Ok(Self(vec![]));
274        }
275
276        let params = query_params
277            .keys()
278            .map(|param_name| {
279                let value = query_params.get(param_name).unwrap();
280
281                let chain = param_name
282                    .split('.')
283                    .map(std::string::ToString::to_string)
284                    .collect::<Vec<String>>();
285
286                if chain.is_empty() {
287                    return Err(ParseError::InvalidParameter(param_name.clone()));
288                }
289
290                let name_and_modifier = chain[0].split(':').collect::<Vec<&str>>();
291
292                if name_and_modifier.len() > 2 || name_and_modifier.is_empty() {
293                    return Err(ParseError::InvalidParameter(param_name.clone()));
294                }
295
296                let name = name_and_modifier[0].to_string();
297
298                let param = Parameter {
299                    name,
300                    modifier: name_and_modifier
301                        .get(1)
302                        .map(std::string::ToString::to_string),
303                    value: value
304                        .split(',')
305                        .map(std::string::ToString::to_string)
306                        .collect(),
307                    chains: if chain.len() > 1 {
308                        Some(chain[1..].to_vec())
309                    } else {
310                        None
311                    },
312                };
313
314                if RESULT_PARAMETERS.contains(&param.name.as_str()) {
315                    Ok(ParsedParameter::Result(param))
316                } else {
317                    Ok(ParsedParameter::Resource(param))
318                }
319            })
320            .collect::<Result<Vec<ParsedParameter>, ParseError>>()?;
321
322        Ok(Self(params))
323    }
324}
325
326#[must_use]
327pub fn parse_prefix(v: &str) -> (Option<&str>, &str) {
328    if v.len() < 3 {
329        return (None, v);
330    }
331
332    let sub_str = &v[..2];
333    let remainder = &v[2..];
334
335    match sub_str {
336        "lt" | "le" | "gt" | "ge" | "eq" | "ne" => (Some(sub_str), remainder),
337        _ => (None, v),
338    }
339}
340
341#[cfg(test)]
342mod tests {
343    use super::*;
344
345    #[test]
346    fn test_parse_prefix() {
347        let cases = vec![
348            ("lt5.0", (Some("lt"), "5.0")),
349            ("le10", (Some("le"), "10")),
350            ("gt3.14", (Some("gt"), "3.14")),
351            ("ge2.71", (Some("ge"), "2.71")),
352            ("eq42", (Some("eq"), "42")),
353            ("ne0", (Some("ne"), "0")),
354            ("5.0", (None, "5.0")),
355            ("10", (None, "10")),
356        ];
357
358        for (input, expected) in cases {
359            let result = parse_prefix(input);
360            assert_eq!(result, expected, "Failed for input: {input}");
361        }
362    }
363
364    #[test]
365    fn test_parse_parameters() {
366        let query_string = "?name=John,Doe&_count=10&address.city=NewYork&status:exact=active";
367        let parsed_params = ParsedParameters::try_from(query_string).unwrap();
368
369        assert_eq!(parsed_params.parameters().len(), 4);
370
371        match parsed_params.get("name") {
372            Some(ParsedParameter::Resource(param)) => {
373                assert_eq!(param.name, "name");
374                assert_eq!(param.value, vec!["John", "Doe"]);
375                assert!(param.modifier.is_none());
376                assert!(param.chains.is_none());
377            }
378            _ => panic!("Expected Resource parameter"),
379        }
380
381        match parsed_params.get("_count") {
382            Some(ParsedParameter::Result(param)) => {
383                assert_eq!(param.name, "_count");
384                assert_eq!(param.value, vec!["10"]);
385                assert!(param.modifier.is_none());
386                assert!(param.chains.is_none());
387            }
388            _ => panic!("Expected Result parameter"),
389        }
390
391        match parsed_params.get("address") {
392            Some(ParsedParameter::Resource(param)) => {
393                assert_eq!(param.name, "address");
394                assert_eq!(param.value, vec!["NewYork"]);
395                assert!(param.modifier.is_none());
396                assert_eq!(param.chains, Some(vec!["city".to_string()]));
397            }
398            _ => panic!("Expected Resource parameter"),
399        }
400
401        match parsed_params.get("status") {
402            Some(ParsedParameter::Resource(param)) => {
403                assert_eq!(param.name, "status");
404                assert_eq!(param.value, vec!["active"]);
405                assert_eq!(param.modifier, Some("exact".to_string()));
406                assert!(param.chains.is_none());
407            }
408            _ => panic!("Expected Resource parameter"),
409        }
410    }
411}