1mod enum_test;
19mod pattern_test;
20mod validator_test;
21
22use crate::model::parse;
23use crate::model::parse::{
24 ComponentsObject, Format, In, OpenAPI, Properties, Request, Type, TypeOrUnion,
25};
26use crate::observability::RequestContext;
27use anyhow::{anyhow, Context, Result};
28use base64::{engine::general_purpose, Engine};
29use chrono::{DateTime, NaiveDate, NaiveTime};
30use regex::Regex;
31use serde_json::{Map, Value};
32use std::collections::{HashMap, HashSet};
33use std::net::{Ipv4Addr, Ipv6Addr};
34use std::string::String;
35use validator::ValidateEmail;
36
37pub trait ValidateRequest {
38 fn header(&self, _: &OpenAPI) -> Result<()>;
39 fn method(&self, _: &OpenAPI) -> Result<()>;
40 fn query(&self, _: &OpenAPI) -> Result<()>;
41 fn path(&self, _: &OpenAPI) -> Result<()>;
42 fn body(&self, _: &OpenAPI) -> Result<()>;
43 fn context(&self) -> RequestContext;
44}
45
46pub fn method(path: &str, method: &str, open_api: &OpenAPI) -> Result<()> {
47 let path = open_api.paths.get(path).context("Path not found")?;
48
49 if !path.operations.contains_key(method) {
50 return Err(anyhow::anyhow!("Path is empty"));
51 }
52
53 Ok(())
54}
55
56pub fn path(path: &str, uri: &str, open_api: &OpenAPI) -> Result<()> {
57 let path_item = open_api.paths.get(path).context("Path not found")?;
58 let empty_vec = vec![];
59 let parameters = path_item
60 .operations
61 .get("get")
62 .and_then(|p| p.parameters.as_ref())
63 .unwrap_or(&empty_vec);
64
65 for parameter in parameters {
66 if parameter.r#ref.is_some() {
67 continue;
69 }
70
71 if let (Some(name), Some(r#in)) = (¶meter.name, ¶meter.r#in) {
72 if *r#in != In::Path {
73 continue;
74 }
75 if let Some(schema) = ¶meter.schema {
76 validate_field_format(name, &Value::from(uri), schema.format.as_ref())?;
77 }
78 }
79 }
80
81 Ok(())
82}
83
84fn process_schema_refs(
85 schema: &parse::Schema,
86 fields: &Map<String, Value>,
87 requireds: &mut HashSet<String>,
88 open_api: &OpenAPI,
89) -> Result<()> {
90 if let Some(components) = &open_api.components {
91 for schema_ref in collect_refs(schema) {
92 requireds.extend(extract_required_and_validate_props(
93 fields, schema_ref, components,
94 )?);
95 }
96 }
97 Ok(())
98}
99
100fn validate_required_fields(
101 requireds: &HashSet<String>,
102 query_pairs: &HashMap<String, String>,
103) -> Result<()> {
104 for key in requireds {
105 if !query_pairs.contains_key(key) {
106 return Err(anyhow!("Missing required query parameter: '{}'", key));
107 }
108 }
109 Ok(())
110}
111
112pub fn query(path: &str, query_pairs: &HashMap<String, String>, open_api: &OpenAPI) -> Result<()> {
113 let path_base = open_api
114 .paths
115 .get(path)
116 .context("Path not found in OpenAPI specification")?;
117 let empty_vec = vec![];
118
119 let all_parameters: Vec<&parse::Parameter> = path_base
120 .operations
121 .values()
122 .flat_map(|op| op.parameters.as_ref().unwrap_or(&empty_vec))
123 .chain(path_base.parameters.as_ref().unwrap_or(&empty_vec))
124 .collect();
125
126 let fields: Map<String, Value> = query_pairs
127 .iter()
128 .map(|(k, v)| (k.clone(), Value::from(v.clone())))
129 .collect();
130
131 let mut required_fields: HashSet<String> = HashSet::new();
132
133 for parameter in &all_parameters {
134 if let Some(param_ref) = ¶meter.r#ref {
135 if let Some(components) = &open_api.components {
136 required_fields.extend(extract_required_and_validate_props(
137 &fields, param_ref, components,
138 )?);
139 }
140 continue;
141 }
142
143 let (Some(name), Some(In::Query)) = (¶meter.name, ¶meter.r#in) else {
144 continue;
145 };
146
147 match query_pairs.get(name) {
148 Some(value) => {
149 if parameter.required && value.trim().is_empty() {
150 return Err(anyhow!(
151 "Required query parameter '{}' cannot be empty",
152 name
153 ));
154 }
155
156 let json_value = Value::from(value.as_str());
157
158 if let Some(enum_values) = ¶meter.r#enum {
159 validate_enum_value(name, &json_value, enum_values)?;
160 }
161
162 if let Some(param_type) = ¶meter.r#type {
163 validate_field_type(name, &json_value, Some(param_type.clone()))?;
164 }
165
166 if let Some(schema) = ¶meter.schema {
167 validate_field_format(name, &json_value, schema.format.as_ref())?;
168
169 if let Some(enum_values) = &schema.r#enum {
170 validate_enum_value(name, &json_value, enum_values)?;
171 }
172
173 if let Some(schema_type) = &schema.r#type {
174 validate_field_type(name, &json_value, Some(schema_type.clone()))?;
175 }
176
177 validate_pattern(name, &json_value, schema.pattern.as_ref())?;
178
179 process_schema_refs(schema, &fields, &mut required_fields, open_api)?;
180
181 validate_string_constraints(name, &json_value, schema)?;
182
183 validate_numeric_constraints(name, &json_value, schema)?;
184 }
185
186 validate_pattern(name, &json_value, parameter.pattern.as_ref())?;
187 }
188 None => {
189 if parameter.required {
190 return Err(anyhow!("Required query parameter '{}' is missing", name));
191 }
192 }
193 }
194 }
195
196 validate_required_fields(&required_fields, query_pairs)?;
197
198 Ok(())
199}
200
201pub fn body(path: &str, fields: Value, open_api: &OpenAPI) -> Result<()> {
202 let path_base = open_api
203 .paths
204 .get(path)
205 .context("Path not found in OpenAPI specification")?;
206
207 let request = path_base.operations.iter().find_map(|(method, operation)| {
208 if matches!(method.as_str(), "post" | "put" | "patch" | "delete") {
209 operation.request.as_ref()
210 } else {
211 None
212 }
213 });
214
215 if let Some(request) = request {
216 if request.required && matches!(fields, Value::Null) {
217 return Err(anyhow!("Request body is required but was not provided"));
218 }
219
220 let refs: Vec<&str> = request
221 .content
222 .values()
223 .flat_map(|media| collect_refs(&media.schema))
224 .collect();
225
226 let schema_info = get_schema_info(&refs, open_api);
227 let expected_type = schema_info
228 .as_ref()
229 .and_then(|schema| schema.r#type.clone());
230
231 match fields {
232 Value::Object(ref map) => {
233 ensure_type(&expected_type, Type::Object)?;
234 validate_object_body(map, request, &refs, open_api)?;
235 }
236 Value::Array(ref arr) => {
237 ensure_type(&expected_type, Type::Array)?;
238
239 if let Some(schema) = &schema_info {
240 validate_array_length_with_schema(arr.len(), schema)?;
241 }
242
243 validate_array_items(arr, request, &refs, open_api)?;
244 }
245 Value::String(_) | Value::Number(_) | Value::Bool(_) => {
246 if let Some(type_or_union) = &expected_type {
247 validate_field_type("request_body", &fields, Some(type_or_union.clone()))?;
248 }
249
250 for media_type in request.content.values() {
251 if let Some(schema_type) = &media_type.schema.r#type {
252 validate_field_type("request_body", &fields, Some(schema_type.clone()))?;
253 }
254
255 if let Some(format) = &media_type.schema.format {
256 validate_field_format("request_body", &fields, Some(format))?;
257 }
258
259 if let Some(enum_values) = &media_type.schema.r#enum {
260 validate_enum_value("request_body", &fields, enum_values)?;
261 }
262 }
263 }
264 Value::Null => {
265 if request.required {
266 return Err(anyhow!("Request body is required but null was provided"));
267 }
268 }
269 }
270 }
271
272 Ok(())
273}
274
275fn get_schema_info<'a>(
276 refs: &[&str],
277 open_api: &'a OpenAPI,
278) -> Option<&'a parse::ComponentSchemaBase> {
279 open_api.components.as_ref().and_then(|components| {
280 refs.iter().find_map(|schema_ref| {
281 schema_ref
282 .rsplit('/')
283 .next()
284 .and_then(|schema_name| components.schemas.get(schema_name))
285 })
286 })
287}
288
289fn validate_object_body(
290 fields: &Map<String, Value>,
291 request: &Request,
292 refs: &[&str],
293 open_api: &OpenAPI,
294) -> Result<()> {
295 for (key, media_type) in &request.content {
296 if let Some(field) = fields.get(key) {
297 let type_or_union = media_type.schema.r#type.clone();
298 validate_field_type(key, field, type_or_union)?;
299 if media_type.schema.r#type == Some(TypeOrUnion::Single(Type::String)) {
300 validate_field_format(key, field, media_type.schema.format.as_ref())?;
301 }
302 }
303 }
304
305 let mut requireds = HashSet::new();
306
307 if let Some(components) = &open_api.components {
308 for schema_ref in refs {
309 requireds.extend(extract_required_and_validate_props(
310 fields, schema_ref, components,
311 )?);
312 }
313 }
314
315 for key in &requireds {
316 if !fields.contains_key(key) {
317 return Err(anyhow!("Missing required request body field: '{}'", key));
318 }
319 }
320
321 Ok(())
322}
323
324fn validate_array_items(
325 arr: &[Value],
326 request: &Request,
327 refs: &[&str],
328 open_api: &OpenAPI,
329) -> Result<()> {
330 for (index, item) in arr.iter().enumerate() {
331 let map = item
332 .as_object()
333 .with_context(|| format!("Array item at index {index} must be an object"))?;
334 validate_map(map, request, refs, open_api)?;
335 }
336 Ok(())
337}
338
339fn validate_array_length_with_schema(
340 length: usize,
341 schema: &parse::ComponentSchemaBase,
342) -> Result<()> {
343 if let Some(min) = schema.min_items {
344 if length < min as usize {
345 return Err(anyhow!(
346 "The array must have at least {} items, but got {}",
347 min,
348 length
349 ));
350 }
351 }
352
353 if let Some(max) = schema.max_items {
354 if length > max as usize {
355 return Err(anyhow!(
356 "The array must have at most {} items, but got {}",
357 max,
358 length
359 ));
360 }
361 }
362
363 Ok(())
364}
365
366fn ensure_type(actual: &Option<TypeOrUnion>, expected: Type) -> Result<()> {
367 if let Some(type_or_union) = actual {
368 match type_or_union {
369 TypeOrUnion::Single(t) => {
370 if *t != expected {
371 return Err(anyhow!(
372 "Expected request body to be a {:?}, got {:?}",
373 expected,
374 t
375 ));
376 }
377 }
378 TypeOrUnion::Union(types) => {
379 if !types.contains(&expected) {
380 return Err(anyhow!(
381 "Expected request body to be a {:?}, but union types {:?} don't include it",
382 expected,
383 types
384 ));
385 }
386 }
387 }
388 }
389 Ok(())
390}
391
392fn validate_map(
393 fields: &Map<String, Value>,
394 request: &Request,
395 refs: &[&str],
396 open_api: &OpenAPI,
397) -> Result<()> {
398 for (key, media_type) in &request.content {
399 if let Some(field) = fields.get(key) {
400 let type_or_union = media_type.schema.r#type.clone();
401 validate_field_type(key, field, type_or_union)?;
402 if media_type.schema.r#type == Some(TypeOrUnion::Single(Type::String)) {
403 validate_field_format(key, field, media_type.schema.format.as_ref())?;
404 }
405 }
406 }
407
408 let mut requireds = HashSet::new();
409
410 if let Some(components) = &open_api.components {
411 for schema_ref in refs {
412 requireds.extend(extract_required_and_validate_props(
413 fields, schema_ref, components,
414 )?);
415 }
416 }
417
418 for key in &requireds {
419 if !fields.contains_key(key) {
420 return Err(anyhow!("Missing required request body field: '{}'", key));
421 }
422 }
423
424 Ok(())
425}
426
427fn validate_field_format(key: &str, value: &Value, format: Option<&Format>) -> Result<()> {
428 let Some(str_val) = value.as_str() else {
429 return Err(anyhow::anyhow!("this value must be string '{}'", key));
430 };
431
432 match format {
433 Some(Format::Email) => {
434 if !str_val.validate_email() {
435 return Err(format_error("Email", key, str_val));
436 }
437 }
438 Some(Format::Time) => {
439 NaiveTime::parse_from_str(str_val, "%H:%M:%S")
440 .map_err(|_| format_error("Time", key, str_val))?;
441 }
442 Some(Format::Date) => {
443 NaiveDate::parse_from_str(str_val, "%Y-%m-%d")
444 .map_err(|_| format_error("Date", key, str_val))?;
445 }
446 Some(Format::DateTime) => {
447 DateTime::parse_from_rfc3339(str_val)
448 .map_err(|_| format_error("DateTime", key, str_val))?;
449 }
450 Some(Format::UUID) => {
451 uuid::Uuid::parse_str(str_val).map_err(|_| format_error("UUID", key, str_val))?;
452 }
453 Some(Format::IPV4) => {
454 str_val
455 .parse::<Ipv4Addr>()
456 .map_err(|_| format_error("IPv4", key, str_val))?;
457 }
458 Some(Format::IPV6) => {
459 str_val
460 .parse::<Ipv6Addr>()
461 .map_err(|_| format_error("IPV6", key, str_val))?;
462 }
463 None => {}
464 _ => {
465 return Err(anyhow::anyhow!(
466 "Unsupported format '{:?}' for query parameter '{}'",
467 format,
468 key
469 ));
470 }
471 }
472 Ok(())
473}
474
475fn validate_enum_value(key: &str, value: &Value, enum_values: &[serde_yaml::Value]) -> Result<()> {
476 for enum_val in enum_values {
477 if values_equal(value, enum_val) {
478 return Ok(());
479 }
480 }
481
482 let enum_strings: Vec<String> = enum_values.iter().map(format_yaml_value).collect();
483
484 Err(anyhow!(
485 "Value '{}' for field '{}' is not in allowed enum values: [{}]",
486 format_json_value(value),
487 key,
488 enum_strings.join(", ")
489 ))
490}
491
492fn values_equal(json_val: &Value, yaml_val: &serde_yaml::Value) -> bool {
493 match (json_val, yaml_val) {
494 (Value::String(s1), serde_yaml::Value::String(s2)) => s1 == s2,
495 (Value::Number(n1), serde_yaml::Value::Number(n2)) => {
496 if let (Some(i1), Some(i2)) = (n1.as_i64(), n2.as_i64()) {
497 i1 == i2
498 } else if let (Some(f1), Some(f2)) = (n1.as_f64(), n2.as_f64()) {
499 (f1 - f2).abs() < f64::EPSILON
500 } else {
501 false
502 }
503 }
504 (Value::Bool(b1), serde_yaml::Value::Bool(b2)) => b1 == b2,
505 (Value::Null, serde_yaml::Value::Null) => true,
506 (Value::String(s), serde_yaml::Value::Number(n)) => {
507 if let Ok(parsed_int) = s.parse::<i64>() {
508 if let Some(yaml_int) = n.as_i64() {
509 return parsed_int == yaml_int;
510 }
511 }
512 if let Ok(parsed_float) = s.parse::<f64>() {
513 if let Some(yaml_float) = n.as_f64() {
514 return (parsed_float - yaml_float).abs() < f64::EPSILON;
515 }
516 }
517 false
518 }
519 (Value::String(s), serde_yaml::Value::Bool(b)) => match s.to_lowercase().as_str() {
520 "true" => *b,
521 "false" => !*b,
522 _ => false,
523 },
524 (Value::Number(n), serde_yaml::Value::String(s)) => {
525 if let Some(int_val) = n.as_i64() {
526 s == &int_val.to_string()
527 } else if let Some(float_val) = n.as_f64() {
528 s == &float_val.to_string()
529 } else {
530 false
531 }
532 }
533 (Value::Bool(b), serde_yaml::Value::String(s)) => match s.to_lowercase().as_str() {
534 "true" => *b,
535 "false" => !*b,
536 _ => false,
537 },
538 _ => false,
539 }
540}
541
542fn format_yaml_value(value: &serde_yaml::Value) -> String {
543 match value {
544 serde_yaml::Value::String(s) => format!("\"{s}\""),
545 serde_yaml::Value::Number(n) => n.to_string(),
546 serde_yaml::Value::Bool(b) => b.to_string(),
547 serde_yaml::Value::Null => "null".to_string(),
548 _ => format!("{value:?}"),
549 }
550}
551
552fn format_json_value(value: &Value) -> String {
553 match value {
554 Value::String(s) => format!("\"{s}\""),
555 Value::Number(n) => n.to_string(),
556 Value::Bool(b) => b.to_string(),
557 Value::Null => "null".to_string(),
558 _ => format!("{value:?}"),
559 }
560}
561fn validate_field_type(key: &str, value: &Value, field_type: Option<TypeOrUnion>) -> Result<()> {
562 use Type::*;
563
564 match field_type {
565 Some(TypeOrUnion::Single(Object)) => {
566 if !value.is_object() {
567 return Err(anyhow!("the value of '{}' must be an Object", key));
568 }
569 }
570 Some(TypeOrUnion::Single(String)) => {
571 if !value.is_string() {
572 return Err(anyhow!("the value of '{}' must be a String", key));
573 }
574 }
575 Some(TypeOrUnion::Single(Integer)) => {
576 if !value.is_i64() {
577 if let Some(str_val) = value.as_str() {
578 if str_val.parse::<i64>().is_err() {
579 return Err(anyhow!("the value of '{}' must be an Integer", key));
580 }
581 } else {
582 return Err(anyhow!("the value of '{}' must be an Integer", key));
583 }
584 }
585 }
586 Some(TypeOrUnion::Single(Number)) => {
587 if !value.is_number() {
588 if let Some(str_val) = value.as_str() {
589 if str_val.parse::<f64>().is_err() {
590 return Err(anyhow!("the value of '{}' must be a Number", key));
591 }
592 } else {
593 return Err(anyhow!("the value of '{}' must be a Number", key));
594 }
595 }
596 }
597 Some(TypeOrUnion::Single(Array)) => {
598 if !value.is_array() {
599 return Err(anyhow!("the value of '{}' must be an Array", key));
600 }
601 }
602 Some(TypeOrUnion::Single(Boolean)) => {
603 if !value.is_boolean() {
604 if let Some(str_val) = value.as_str() {
605 match str_val.to_lowercase().as_str() {
606 "true" | "false" => {}
607 _ => {
608 return Err(anyhow!("the value of '{}' must be a Boolean", key));
609 }
610 }
611 } else {
612 return Err(anyhow!("the value of '{}' must be a Boolean", key));
613 }
614 }
615 }
616 Some(TypeOrUnion::Single(Null)) => {
617 if !value.is_null() {
618 return Err(anyhow!("the value of '{}' must be Null", key));
619 }
620 }
621 Some(TypeOrUnion::Single(Base64)) => {
622 let str_val = value
623 .as_str()
624 .ok_or_else(|| anyhow!("the value of '{}' must be a string", key))?;
625
626 if str_val.trim().is_empty() {
627 return Err(anyhow!("the value of '{}' must not be empty", key));
628 }
629
630 if general_purpose::STANDARD.decode(str_val).is_err() {
631 return Err(anyhow!("the value of '{}' must be valid Base64", key));
632 }
633 }
634 Some(TypeOrUnion::Single(Binary)) => {
635 if !value.is_string() {
636 return Err(anyhow!(
637 "the value of '{}' must be a String for binary data",
638 key
639 ));
640 }
641 }
642 Some(TypeOrUnion::Union(types)) => {
643 let mut valid = false;
644 for single_type in types {
645 if validate_single_type_match(value, &single_type) {
646 valid = true;
647 break;
648 }
649 }
650 if !valid {
651 return Err(anyhow!(
652 "the value of '{}' must match one of the union types",
653 key
654 ));
655 }
656 }
657 None => {}
658 }
659
660 Ok(())
661}
662
663fn validate_single_type_match(value: &Value, field_type: &Type) -> bool {
664 use Type::*;
665 match field_type {
666 Object => value.is_object(),
667 String | Binary => value.is_string(),
668 Integer => value.is_i64(),
669 Number => value.is_number(),
670 Array => value.is_array(),
671 Boolean => value.is_boolean(),
672 Null => value.is_null(),
673 Base64 => {
674 if let Some(str_val) = value.as_str() {
675 !str_val.trim().is_empty() && general_purpose::STANDARD.decode(str_val).is_ok()
676 } else {
677 false
678 }
679 }
680 }
681}
682
683fn validate_field_length_limit(key: &str, value: &Value, properties: &Properties) -> Result<()> {
684 use TypeOrUnion::*;
685
686 match &properties.r#type {
687 Some(Single(type_)) => {
688 validate_single_type(key, value, type_, properties)?;
689 }
690 Some(Union(types)) => {
691 validate_union_types(key, value, types, properties)?;
692 }
693 None => {}
694 }
695
696 Ok(())
697}
698
699fn validate_single_type(
700 key: &str,
701 value: &Value,
702 type_: &Type,
703 properties: &Properties,
704) -> Result<()> {
705 use Type::*;
706
707 match type_ {
708 String | Base64 | Binary => {
709 let str_val = value
710 .as_str()
711 .ok_or_else(|| anyhow!("The value of '{}' must be a String", key))?;
712 validate_string_length(key, str_val, properties)?;
713 }
714 Integer => {
715 let int_val = value
716 .as_i64()
717 .ok_or_else(|| anyhow!("The value of '{}' must be an Integer", key))?;
718 validate_numeric_range(key, int_val as f64, properties)?;
719 }
720 Number => {
721 let num_val = value
722 .as_f64()
723 .ok_or_else(|| anyhow!("The value of '{}' must be a Number", key))?;
724 validate_numeric_range(key, num_val, properties)?;
725 }
726 Array => {
727 if !value.is_array() {
728 return Err(anyhow!("The value of '{}' must be an Array", key));
729 }
730 let arr_len = value.as_array().unwrap().len();
731 validate_array_length(key, arr_len, properties)?;
732 }
733 Boolean => {
734 if !value.is_boolean() {
735 return Err(anyhow!("The value of '{}' must be a Boolean", key));
736 }
737 }
738 Null => {
739 if !value.is_null() {
740 return Err(anyhow!("The value of '{}' must be null", key));
741 }
742 }
743 Object => {
744 if !value.is_object() {
745 return Err(anyhow!("The value of '{}' must be an Object", key));
746 }
747 }
748 }
749
750 Ok(())
751}
752
753fn validate_union_types(
754 key: &str,
755 value: &Value,
756 types: &[Type],
757 properties: &Properties,
758) -> Result<()> {
759 let mut validation_errors = Vec::new();
760 let mut type_matched = false;
761
762 for type_ in types {
763 match validate_single_type(key, value, type_, properties) {
764 Ok(()) => {
765 type_matched = true;
766 break;
767 }
768 Err(e) => {
769 validation_errors.push(e.to_string());
770 }
771 }
772 }
773
774 if !type_matched {
775 let type_names: Vec<String> = types.iter().map(|t| format!("{t:?}")).collect();
776 return Err(anyhow!(
777 "The value of '{}' does not match any of the union types [{}]. Validation errors: {}",
778 key,
779 type_names.join(", "),
780 validation_errors.join("; ")
781 ));
782 }
783
784 Ok(())
785}
786
787fn validate_string_length(key: &str, str_val: &str, properties: &Properties) -> Result<()> {
788 let length = str_val.len();
789
790 if let Some(min) = properties.min_length {
791 if length < usize::try_from(min)? {
792 return Err(anyhow!(
793 "The length of '{}' must be at least {} characters, but got {}",
794 key,
795 min,
796 length
797 ));
798 }
799 }
800
801 if let Some(max) = properties.max_length {
802 if length > usize::try_from(max)? {
803 return Err(anyhow!(
804 "The length of '{}' must be at most {} characters, but got {}",
805 key,
806 max,
807 length
808 ));
809 }
810 }
811
812 Ok(())
813}
814
815fn validate_numeric_range(key: &str, value: f64, properties: &Properties) -> Result<()> {
816 if let Some(min) = properties.minimum {
817 if value < min {
818 return Err(anyhow!(
819 "The value of '{}' must be >= {}, but got {}",
820 key,
821 min,
822 value
823 ));
824 }
825 }
826
827 if let Some(max) = properties.maximum {
828 if value > max {
829 return Err(anyhow!(
830 "The value of '{}' must be <= {}, but got {}",
831 key,
832 max,
833 value
834 ));
835 }
836 }
837
838 Ok(())
839}
840
841fn validate_array_length(key: &str, length: usize, properties: &Properties) -> Result<()> {
842 if let Some(min) = properties.min_items {
843 if length < usize::try_from(min)? {
844 return Err(anyhow!(
845 "The array '{}' must have at least {} items, but got {}",
846 key,
847 min,
848 length
849 ));
850 }
851 }
852
853 if let Some(max) = properties.max_items {
854 if length > usize::try_from(max)? {
855 return Err(anyhow!(
856 "The array '{}' must have at most {} items, but got {}",
857 key,
858 max,
859 length
860 ));
861 }
862 }
863
864 Ok(())
865}
866
867fn format_error(kind: &str, key: &str, value: &str) -> anyhow::Error {
868 anyhow::anyhow!(
869 "Invalid {} format for query parameter '{}': '{}'",
870 kind,
871 key,
872 value
873 )
874}
875
876fn extract_required_and_validate_props(
877 fields: &Map<String, Value>,
878 schema_ref: &str,
879 components: &ComponentsObject,
880) -> Result<HashSet<String>> {
881 let filename = schema_ref
882 .rsplit('/')
883 .next()
884 .ok_or_else(|| anyhow!("Invalid schema reference: '{}'", schema_ref))?;
885
886 let mut requireds = HashSet::new();
887
888 if let Some(schema) = components.schemas.get(filename) {
889 requireds.extend(schema.required.iter().cloned());
890 validate_properties(fields, &schema.properties)?;
891
892 if let Some(items) = &schema.items {
893 requireds.extend(items.required.iter().cloned());
894 validate_properties(fields, &items.properties)?;
895 }
896 }
897
898 Ok(requireds)
899}
900
901fn validate_properties(
902 fields: &Map<String, Value>,
903 properties: &Option<HashMap<String, Properties>>,
904) -> Result<()> {
905 if let Some(properties) = properties {
906 for (key, prop) in properties {
907 if let Some(value) = fields.get(key) {
908 validate_field_type(key, value, prop.r#type.clone())?;
909
910 if let Some(TypeOrUnion::Single(Type::String)) = prop.r#type {
911 validate_field_format(key, value, prop.format.as_ref())?;
912 }
913
914 if let Some(enum_values) = &prop.r#enum {
915 validate_enum_value(key, value, enum_values)?;
916 }
917
918 validate_pattern(key, value, prop.pattern.as_ref())?;
919
920 validate_field_length_limit(key, value, prop)?;
921 }
922 validate_properties(fields, &prop.properties)?;
923 }
924 }
925
926 Ok(())
927}
928
929fn collect_refs(schema: &parse::Schema) -> Vec<&str> {
930 let mut refs = Vec::new();
931 if let Some(r) = &schema.r#ref {
932 refs.push(r.as_str());
933 }
934 if let Some(one_of) = &schema.one_of {
935 for s in one_of {
936 if let Some(r) = &s.r#ref {
937 refs.push(r.as_str());
938 }
939 }
940 }
941 if let Some(all_of) = &schema.all_of {
942 for s in all_of {
943 if let Some(r) = &s.r#ref {
944 refs.push(r.as_str());
945 }
946 }
947 }
948 refs
949}
950
951fn validate_string_constraints(key: &str, value: &Value, schema: &parse::Schema) -> Result<()> {
952 if let Some(str_val) = value.as_str() {
953 if let Some(min_len) = schema.min_length {
954 if str_val.len() < usize::try_from(min_len)? {
955 return Err(anyhow!(
956 "Parameter '{}' must be at least {} characters long, but got {}",
957 key,
958 min_len,
959 str_val.len()
960 ));
961 }
962 }
963
964 if let Some(max_len) = schema.max_length {
965 if str_val.len() > usize::try_from(max_len)? {
966 return Err(anyhow!(
967 "Parameter '{}' must be at most {} characters long, but got {}",
968 key,
969 max_len,
970 str_val.len()
971 ));
972 }
973 }
974 }
975 Ok(())
976}
977
978fn validate_numeric_constraints(key: &str, value: &Value, schema: &parse::Schema) -> Result<()> {
979 if let Some(num_val) = value.as_f64() {
980 if let Some(min) = schema.minimum {
981 if num_val < min {
982 return Err(anyhow!(
983 "Parameter '{}' must be >= {}, but got {}",
984 key,
985 min,
986 num_val
987 ));
988 }
989 }
990
991 if let Some(max) = schema.maximum {
992 if num_val > max {
993 return Err(anyhow!(
994 "Parameter '{}' must be <= {}, but got {}",
995 key,
996 max,
997 num_val
998 ));
999 }
1000 }
1001 }
1002 Ok(())
1003}
1004
1005fn validate_pattern(key: &str, value: &Value, pattern: Option<&String>) -> Result<()> {
1006 if let Some(pattern_str) = pattern {
1007 if let Some(str_val) = value.as_str() {
1008 let regex = Regex::new(pattern_str).map_err(|e| {
1009 anyhow!(
1010 "Invalid regex pattern '{}' for field '{}': {}",
1011 pattern_str,
1012 key,
1013 e
1014 )
1015 })?;
1016
1017 if !regex.is_match(str_val) {
1018 return Err(anyhow!(
1019 "Value '{}' for field '{}' does not match the required pattern '{}'",
1020 str_val,
1021 key,
1022 pattern_str
1023 ));
1024 }
1025 }
1026 }
1027 Ok(())
1028}