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