haste_fhir_client/
url.rs

1use haste_fhir_operation_error::derive::OperationOutcomeError;
2use std::{collections::HashMap, fmt::Display};
3
4#[derive(Debug, Clone)]
5pub struct Parameter {
6    pub name: String,
7    pub value: Vec<String>,
8    pub modifier: Option<String>,
9    pub chains: Option<Vec<String>>,
10}
11
12/// Represnet both resource parameters IE Patient.name and
13/// result parameters IE _count
14#[derive(Debug, Clone)]
15pub enum ParsedParameter {
16    Result(Parameter),
17    Resource(Parameter),
18}
19
20#[derive(Debug, OperationOutcomeError)]
21pub enum ParseError {
22    #[fatal(
23        code = "invalid",
24        diagnostic = "Error parsing query parameters '{arg0}'"
25    )]
26    InvalidParameter(String),
27}
28
29impl Display for ParseError {
30    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
31        match self {
32            ParseError::InvalidParameter(param) => {
33                write!(f, "Invalid query parameter: {}", param)
34            }
35        }
36    }
37}
38
39impl std::error::Error for ParseError {}
40
41static RESULT_PARAMETERS: &[&str] = &[
42    "_count",
43    "_offset",
44    "_total",
45    "_sort",
46    "_include",
47    "_revinclude",
48    "_summary",
49    "_elements",
50    "_contained",
51    "_containedType",
52];
53
54#[derive(Debug, Clone)]
55pub struct ParsedParameters(Vec<ParsedParameter>);
56
57impl ParsedParameters {
58    pub fn new(params: Vec<ParsedParameter>) -> Self {
59        Self(params)
60    }
61    pub fn parameters(&self) -> &Vec<ParsedParameter> {
62        &self.0
63    }
64    pub fn get(&self, name: &str) -> Option<&ParsedParameter> {
65        self.0.iter().find(|p| match p {
66            ParsedParameter::Resource(param) | ParsedParameter::Result(param) => param.name == name,
67        })
68    }
69}
70
71impl TryFrom<&str> for ParsedParameters {
72    type Error = ParseError;
73    fn try_from(query_string: &str) -> Result<Self, ParseError> {
74        let mut query_string = query_string;
75        if query_string.is_empty() {
76            return Ok(Self(vec![]));
77        }
78
79        if query_string.starts_with('?') {
80            query_string = &query_string[1..];
81        }
82
83        let query_map = query_string.split('&').fold(
84            Ok(HashMap::new()),
85            |acc: Result<HashMap<String, String>, ParseError>, pair| {
86                let mut map = acc?;
87                let mut split = pair.splitn(2, '=');
88                let key = split
89                    .next()
90                    .ok_or_else(|| ParseError::InvalidParameter(pair.to_string()))?;
91                let value = split
92                    .next()
93                    .ok_or_else(|| ParseError::InvalidParameter(pair.to_string()))?;
94                map.insert(key.to_string(), value.to_string());
95                Ok(map)
96            },
97        )?;
98
99        Self::try_from(&query_map)
100    }
101}
102
103impl TryFrom<&HashMap<String, String>> for ParsedParameters {
104    type Error = ParseError;
105    fn try_from(query_params: &HashMap<String, String>) -> Result<Self, ParseError> {
106        if query_params.is_empty() {
107            return Ok(Self(vec![]));
108        }
109
110        let params = query_params
111            .keys()
112            .map(|param_name| {
113                let value = query_params.get(param_name).unwrap();
114
115                let chain = param_name
116                    .split('.')
117                    .map(|s| s.to_string())
118                    .collect::<Vec<String>>();
119
120                if chain.is_empty() {
121                    return Err(ParseError::InvalidParameter(param_name.to_string()));
122                }
123
124                let name_and_modifier = chain[0].split(':').collect::<Vec<&str>>();
125
126                if name_and_modifier.len() > 2 || name_and_modifier.is_empty() {
127                    return Err(ParseError::InvalidParameter(param_name.to_string()));
128                }
129
130                let name = name_and_modifier[0].to_string();
131
132                let param = Parameter {
133                    name,
134                    modifier: name_and_modifier.get(1).map(|s| s.to_string()),
135                    value: value.split(',').map(|v| v.to_string()).collect(),
136                    chains: if chain.len() > 1 {
137                        Some(chain[1..].to_vec())
138                    } else {
139                        None
140                    },
141                };
142
143                if RESULT_PARAMETERS.contains(&param.name.as_str()) {
144                    Ok(ParsedParameter::Result(param))
145                } else {
146                    Ok(ParsedParameter::Resource(param))
147                }
148            })
149            .collect::<Result<Vec<ParsedParameter>, ParseError>>()?;
150
151        Ok(Self(params))
152    }
153}
154
155#[cfg(test)]
156mod tests {
157    use super::*;
158
159    #[test]
160    fn test_parse_parameters() {
161        let query_string = "?name=John,Doe&_count=10&address.city=NewYork&status:exact=active";
162        let parsed_params = ParsedParameters::try_from(query_string).unwrap();
163
164        assert_eq!(parsed_params.parameters().len(), 4);
165
166        match parsed_params.get("name") {
167            Some(ParsedParameter::Resource(param)) => {
168                assert_eq!(param.name, "name");
169                assert_eq!(param.value, vec!["John", "Doe"]);
170                assert!(param.modifier.is_none());
171                assert!(param.chains.is_none());
172            }
173            _ => panic!("Expected Resource parameter"),
174        }
175
176        match parsed_params.get("_count") {
177            Some(ParsedParameter::Result(param)) => {
178                assert_eq!(param.name, "_count");
179                assert_eq!(param.value, vec!["10"]);
180                assert!(param.modifier.is_none());
181                assert!(param.chains.is_none());
182            }
183            _ => panic!("Expected Result parameter"),
184        }
185
186        match parsed_params.get("address") {
187            Some(ParsedParameter::Resource(param)) => {
188                assert_eq!(param.name, "address");
189                assert_eq!(param.value, vec!["NewYork"]);
190                assert!(param.modifier.is_none());
191                assert_eq!(param.chains, Some(vec!["city".to_string()]));
192            }
193            _ => panic!("Expected Resource parameter"),
194        }
195
196        match parsed_params.get("status") {
197            Some(ParsedParameter::Resource(param)) => {
198                assert_eq!(param.name, "status");
199                assert_eq!(param.value, vec!["active"]);
200                assert_eq!(param.modifier, Some("exact".to_string()));
201                assert!(param.chains.is_none());
202            }
203            _ => panic!("Expected Resource parameter"),
204        }
205    }
206}