Skip to main content

openapi_rs/validator/
mod.rs

1/*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements.  See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License.  You may obtain a copy of the License at
8 *
9 *     http://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17
18mod 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            // TODO: handle parameter references
68            continue;
69        }
70
71        if let (Some(name), Some(r#in)) = (&parameter.name, &parameter.r#in) {
72            if *r#in != In::Path {
73                continue;
74            }
75            if let Some(schema) = &parameter.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) = &parameter.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)) = (&parameter.name, &parameter.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) = &parameter.r#enum {
159                    validate_enum_value(name, &json_value, enum_values)?;
160                }
161
162                if let Some(param_type) = &parameter.r#type {
163                    validate_field_type(name, &json_value, Some(param_type.clone()))?;
164                }
165
166                if let Some(schema) = &parameter.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}