1use haste_fhir_model::r4::generated::{
2 resources::{
3 OperationDefinitionParameter, OperationOutcome, OperationOutcomeIssue, Parameters,
4 ParametersParameter,
5 },
6 terminology::{BoundCode, IssueSeverity, IssueType, OperationParameterUse},
7};
8use haste_fhir_operation_error::OperationOutcomeError;
9use haste_reflect::MetaValue as _;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum ParameterDirection {
14 In,
15 Out,
16}
17
18fn create_issue(
19 severity: BoundCode<IssueSeverity>,
20 type_: BoundCode<IssueType>,
21 diagnostics: String,
22) -> OperationOutcomeIssue {
23 OperationOutcomeIssue {
24 severity: severity,
25 code: type_,
26 diagnostics: Some(Box::new(diagnostics.into())),
27 ..Default::default()
28 }
29}
30
31pub fn validate_parameters(
33 parameters: &Parameters,
34 operation_params: &[OperationDefinitionParameter],
35 direction: &BoundCode<OperationParameterUse>,
36) -> Result<(), OperationOutcomeError> {
37 let parameter_definitions: Vec<&OperationDefinitionParameter> = operation_params
38 .iter()
39 .filter(|p| &p.use_ == direction)
40 .collect();
41
42 let parameters_to_validate: &[ParametersParameter] =
43 parameters.parameter.as_deref().unwrap_or_default();
44
45 let mut issues: Vec<OperationOutcomeIssue> = Vec::new();
46
47 for parameter_definition in ¶meter_definitions {
49 let name = match parameter_definition.name.value.as_deref() {
50 Some(n) => n,
51 None => continue,
52 };
53
54 let found_parameters: Vec<&ParametersParameter> = parameters_to_validate
55 .iter()
56 .filter(|p| p.name.value.as_deref() == Some(name))
57 .collect();
58
59 let count = found_parameters.len() as i64;
60
61 let min = parameter_definition.min.value.unwrap_or(0);
63 if count < min {
64 issues.push(create_issue(
65 IssueSeverity::error(),
66 IssueType::invariant(),
67 format!(
68 "Parameter '{}' requires at least {} occurrence(s) but only {} were supplied.",
69 name, min, count
70 ),
71 ));
72 }
73
74 if let Some(max_str) = parameter_definition.max.value.as_deref() {
76 if max_str != "*" {
77 if let Ok(max) = max_str.parse::<i64>() {
78 if count > max {
79 issues.push(create_issue(IssueSeverity::error(), IssueType::invariant(),
80 format!(
81 "Parameter '{}' allows a maximum of {} occurrence(s) but {} were supplied.",
82 name, max, count
83 )));
84 }
85 }
86 }
87 }
88
89 if let Some(parameter_def_type) = ¶meter_definition.type_ {
93 let type_name = parameter_def_type.as_str();
94 for found_parameter in found_parameters.iter() {
95 let type_ = if let Some(resource) = found_parameter.resource.as_ref() {
96 resource.fhir_type()
97 } else {
98 found_parameter.value.fhir_type()
99 };
100
101 if type_ != type_name.as_deref().unwrap_or_default() {
102 issues.push(create_issue(
103 IssueSeverity::error(),
104 IssueType::invalid(),
105 format!(
106 "Parameter '{}' expects type '{}' but found '{}'.",
107 name,
108 type_name.as_deref().unwrap_or("<unknown>"),
109 type_
110 ),
111 ));
112 }
113 }
114 }
115
116 if let Some(part_defs) = ¶meter_definition.part {
119 for supplied_param in &found_parameters {
120 if let Some(supplied_parts) = &supplied_param.part {
121 let parts_as_parameters = Parameters {
122 parameter: Some(supplied_parts.clone()),
123 ..Default::default()
124 };
125 validate_parameters(&parts_as_parameters, part_defs, &direction)?;
126 }
127 }
128 }
129 }
130
131 for supplied_param in parameters_to_validate {
133 let name = supplied_param.name.value.as_deref().unwrap_or("<unnamed>");
134 let defined = parameter_definitions
135 .iter()
136 .any(|d| d.name.value.as_deref() == Some(name));
137 if !defined {
138 let display_direction = direction.as_str();
139 issues.push(create_issue(
140 IssueSeverity::error(),
141 IssueType::invalid(),
142 format!(
143 "Parameter '{}' is not defined for the '{}' direction.",
144 name,
145 display_direction.as_deref().unwrap_or("<unknown>")
146 ),
147 ));
148 }
149 }
150
151 if issues.is_empty() {
152 Ok(())
153 } else {
154 Err(OperationOutcomeError::new(
155 None,
156 OperationOutcome {
157 issue: issues,
158 ..Default::default()
159 },
160 ))
161 }
162}
163
164#[cfg(test)]
165mod tests {
166 use super::*;
167 use haste_fhir_model::r4::generated::{
168 resources::{
169 OperationDefinitionParameter, Parameters, ParametersParameter,
170 ParametersParameterValueTypeChoice, Patient, Practitioner, Resource,
171 },
172 terminology::{AllTypes, OperationParameterUse},
173 types::{FHIRCode, FHIRInteger, FHIRString},
174 };
175
176 fn make_def(
177 name: &str,
178 direction: BoundCode<OperationParameterUse>,
179 min: i64,
180 max: &str,
181 type_: Option<BoundCode<AllTypes>>,
182 ) -> OperationDefinitionParameter {
183 OperationDefinitionParameter {
184 name: Box::new(FHIRCode {
185 value: Some(name.to_string()),
186 ..Default::default()
187 }),
188 use_: direction,
189 min: Box::new(FHIRInteger {
190 value: Some(min),
191 ..Default::default()
192 }),
193 max: Box::new(FHIRString {
194 value: Some(max.to_string()),
195 ..Default::default()
196 }),
197 type_: type_,
198 ..Default::default()
199 }
200 }
201
202 fn make_param(name: &str) -> ParametersParameter {
203 ParametersParameter {
204 name: Box::new(FHIRString {
205 value: Some(name.to_string()),
206 ..Default::default()
207 }),
208 ..Default::default()
209 }
210 }
211
212 #[test]
213 fn required_param_missing_fails() {
214 let defs = vec![make_def(
215 "subject",
216 OperationParameterUse::in_(),
217 1,
218 "1",
219 None,
220 )];
221 let params = Parameters {
222 parameter: None,
223 ..Default::default()
224 };
225 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::in_()).is_err());
226 }
227
228 #[test]
229 fn required_param_present_passes() {
230 let defs = vec![make_def(
231 "subject",
232 OperationParameterUse::in_(),
233 1,
234 "1",
235 None,
236 )];
237 let params = Parameters {
238 parameter: Some(vec![make_param("subject")]),
239 ..Default::default()
240 };
241 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::in_()).is_ok());
242 }
243
244 #[test]
245 fn extra_param_is_rejected() {
246 let defs = vec![make_def(
247 "subject",
248 OperationParameterUse::in_(),
249 0,
250 "1",
251 None,
252 )];
253 let params = Parameters {
254 parameter: Some(vec![make_param("unknown")]),
255 ..Default::default()
256 };
257 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::in_()).is_err());
258 }
259
260 #[test]
261 fn max_exceeded_fails() {
262 let defs = vec![make_def(
263 "subject",
264 OperationParameterUse::in_(),
265 0,
266 "1",
267 None,
268 )];
269 let params = Parameters {
270 parameter: Some(vec![make_param("subject"), make_param("subject")]),
271 ..Default::default()
272 };
273 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::in_()).is_err());
274 }
275
276 #[test]
277 fn out_direction_ignored_for_in_validation() {
278 let defs = vec![make_def(
280 "result",
281 OperationParameterUse::out(),
282 1,
283 "1",
284 None,
285 )];
286 let params = Parameters {
287 parameter: None,
288 ..Default::default()
289 };
290 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::in_()).is_ok());
292 }
293
294 #[test]
295 fn unbounded_max_passes() {
296 let defs = vec![make_def("note", OperationParameterUse::in_(), 0, "*", None)];
297 let params = Parameters {
298 parameter: Some(vec![
299 make_param("note"),
300 make_param("note"),
301 make_param("note"),
302 ]),
303 ..Default::default()
304 };
305 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::in_()).is_ok());
306 }
307
308 #[test]
309 fn basic_type_validation() {
310 let defs = vec![make_def(
311 "note",
312 OperationParameterUse::in_(),
313 0,
314 "*",
315 Some(AllTypes::string()),
316 )];
317
318 let mut parameter_note = make_param("note");
319 parameter_note.value = Some(ParametersParameterValueTypeChoice::String(Box::new(
320 FHIRString {
321 value: Some("This is a note.".to_string()),
322 ..Default::default()
323 },
324 )));
325
326 let params = Parameters {
327 parameter: Some(vec![parameter_note.clone(), parameter_note.clone()]),
328 ..Default::default()
329 };
330
331 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::in_()).is_ok());
332
333 parameter_note.value = Some(ParametersParameterValueTypeChoice::Integer(Box::new(
334 FHIRInteger {
335 value: Some(42),
336 ..Default::default()
337 },
338 )));
339
340 let params = Parameters {
341 parameter: Some(vec![parameter_note]),
342 ..Default::default()
343 };
344
345 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::in_()).is_err());
346 }
347
348 #[test]
349 fn resource_validation() {
350 let defs = vec![make_def(
351 "note",
352 OperationParameterUse::in_(),
353 0,
354 "*",
355 Some(AllTypes::patient()),
356 )];
357
358 let mut parameter_note = make_param("note");
359 parameter_note.resource = Some(Box::new(Resource::Patient(Patient {
360 ..Default::default()
361 })));
362
363 let params = Parameters {
364 parameter: Some(vec![parameter_note.clone(), parameter_note.clone()]),
365 ..Default::default()
366 };
367
368 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::in_()).is_ok());
369
370 parameter_note.resource = Some(Box::new(Resource::Practitioner(Practitioner {
371 ..Default::default()
372 })));
373
374 let params = Parameters {
375 parameter: Some(vec![parameter_note]),
376 ..Default::default()
377 };
378
379 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::in_()).is_err());
380 }
381
382 #[test]
383 fn test_nested() {
384 let mut parent = make_def("parent", OperationParameterUse::in_(), 1, "1", None);
385
386 parent.part = Some(vec![make_def(
387 "child",
388 OperationParameterUse::in_(),
389 1,
390 "1",
391 Some(AllTypes::string()),
392 )]);
393
394 let defs = vec![parent];
395
396 let mut child_param = make_param("child");
397 child_param.value = Some(ParametersParameterValueTypeChoice::String(Box::new(
398 FHIRString {
399 value: Some("I am a child parameter.".to_string()),
400 ..Default::default()
401 },
402 )));
403
404 let mut parent_param = make_param("parent");
405 parent_param.part = Some(vec![child_param.clone()]);
406
407 let params = Parameters {
408 parameter: Some(vec![parent_param.clone()]),
409 ..Default::default()
410 };
411
412 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::in_()).is_ok());
413
414 child_param.value = Some(ParametersParameterValueTypeChoice::Integer(Box::new(
415 FHIRInteger {
416 value: Some(42),
417 ..Default::default()
418 },
419 )));
420
421 parent_param.part = Some(vec![child_param]);
422
423 let params = Parameters {
424 parameter: Some(vec![parent_param.clone()]),
425 ..Default::default()
426 };
427
428 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::in_()).is_err());
429 }
430}