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#[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(¶m.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}