1use haste_fhir_model::r4::generated::{
2 resources::{
3 OperationDefinitionParameter, OperationOutcome, OperationOutcomeIssue, Parameters,
4 ParametersParameter,
5 },
6 terminology::{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: IssueSeverity,
20 type_: IssueType,
21 diagnostics: String,
22) -> OperationOutcomeIssue {
23 OperationOutcomeIssue {
24 severity: Box::new(severity),
25 code: Box::new(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: &OperationParameterUse,
36) -> Result<(), OperationOutcomeError> {
37 let parameter_definitions: Vec<&OperationDefinitionParameter> = operation_params
38 .iter()
39 .filter(|p| std::mem::discriminant(p.use_.as_ref()) == std::mem::discriminant(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(None),
66 IssueType::Invariant(None),
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(None), IssueType::Invariant(None),
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: Option<String> = parameter_def_type.as_ref().into();
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(None),
104 IssueType::Invalid(None),
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: Option<String> = (direction).into();
139 issues.push(create_issue(
140 IssueSeverity::Error(None),
141 IssueType::Invalid(None),
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: OperationParameterUse,
179 min: i64,
180 max: &str,
181 type_: Option<Box<AllTypes>>,
182 ) -> OperationDefinitionParameter {
183 OperationDefinitionParameter {
184 name: Box::new(FHIRCode {
185 value: Some(name.to_string()),
186 ..Default::default()
187 }),
188 use_: Box::new(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(None),
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(None)).is_err());
226 }
227
228 #[test]
229 fn required_param_present_passes() {
230 let defs = vec![make_def(
231 "subject",
232 OperationParameterUse::In(None),
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(None)).is_ok());
242 }
243
244 #[test]
245 fn extra_param_is_rejected() {
246 let defs = vec![make_def(
247 "subject",
248 OperationParameterUse::In(None),
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(None)).is_err());
258 }
259
260 #[test]
261 fn max_exceeded_fails() {
262 let defs = vec![make_def(
263 "subject",
264 OperationParameterUse::In(None),
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(None)).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(None),
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(None)).is_ok());
292 }
293
294 #[test]
295 fn unbounded_max_passes() {
296 let defs = vec![make_def(
297 "note",
298 OperationParameterUse::In(None),
299 0,
300 "*",
301 None,
302 )];
303 let params = Parameters {
304 parameter: Some(vec![
305 make_param("note"),
306 make_param("note"),
307 make_param("note"),
308 ]),
309 ..Default::default()
310 };
311 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::In(None)).is_ok());
312 }
313
314 #[test]
315 fn basic_type_validation() {
316 let defs = vec![make_def(
317 "note",
318 OperationParameterUse::In(None),
319 0,
320 "*",
321 Some(Box::new(AllTypes::String(None))),
322 )];
323
324 let mut parameter_note = make_param("note");
325 parameter_note.value = Some(ParametersParameterValueTypeChoice::String(Box::new(
326 FHIRString {
327 value: Some("This is a note.".to_string()),
328 ..Default::default()
329 },
330 )));
331
332 let params = Parameters {
333 parameter: Some(vec![parameter_note.clone(), parameter_note.clone()]),
334 ..Default::default()
335 };
336
337 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::In(None)).is_ok());
338
339 parameter_note.value = Some(ParametersParameterValueTypeChoice::Integer(Box::new(
340 FHIRInteger {
341 value: Some(42),
342 ..Default::default()
343 },
344 )));
345
346 let params = Parameters {
347 parameter: Some(vec![parameter_note]),
348 ..Default::default()
349 };
350
351 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::In(None)).is_err());
352 }
353
354 #[test]
355 fn resource_validation() {
356 let defs = vec![make_def(
357 "note",
358 OperationParameterUse::In(None),
359 0,
360 "*",
361 Some(Box::new(AllTypes::Patient(None))),
362 )];
363
364 let mut parameter_note = make_param("note");
365 parameter_note.resource = Some(Box::new(Resource::Patient(Patient {
366 ..Default::default()
367 })));
368
369 let params = Parameters {
370 parameter: Some(vec![parameter_note.clone(), parameter_note.clone()]),
371 ..Default::default()
372 };
373
374 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::In(None)).is_ok());
375
376 parameter_note.resource = Some(Box::new(Resource::Practitioner(Practitioner {
377 ..Default::default()
378 })));
379
380 let params = Parameters {
381 parameter: Some(vec![parameter_note]),
382 ..Default::default()
383 };
384
385 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::In(None)).is_err());
386 }
387
388 #[test]
389 fn test_nested() {
390 let mut parent = make_def("parent", OperationParameterUse::In(None), 1, "1", None);
391
392 parent.part = Some(vec![make_def(
393 "child",
394 OperationParameterUse::In(None),
395 1,
396 "1",
397 Some(Box::new(AllTypes::String(None))),
398 )]);
399
400 let defs = vec![parent];
401
402 let mut child_param = make_param("child");
403 child_param.value = Some(ParametersParameterValueTypeChoice::String(Box::new(
404 FHIRString {
405 value: Some("I am a child parameter.".to_string()),
406 ..Default::default()
407 },
408 )));
409
410 let mut parent_param = make_param("parent");
411 parent_param.part = Some(vec![child_param.clone()]);
412
413 let params = Parameters {
414 parameter: Some(vec![parent_param.clone()]),
415 ..Default::default()
416 };
417
418 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::In(None)).is_ok());
419
420 child_param.value = Some(ParametersParameterValueTypeChoice::Integer(Box::new(
421 FHIRInteger {
422 value: Some(42),
423 ..Default::default()
424 },
425 )));
426
427 parent_param.part = Some(vec![child_param]);
428
429 let params = Parameters {
430 parameter: Some(vec![parent_param.clone()]),
431 ..Default::default()
432 };
433
434 assert!(validate_parameters(¶ms, &defs, &OperationParameterUse::In(None)).is_err());
435 }
436}