1use std::collections::HashMap;
7
8use serde_json::Value;
9use thiserror::Error;
10
11use barbacane_compiler::{Parameter, RequestBody};
12
13#[derive(Debug, Error)]
15pub enum ValidationError2 {
16 #[error("missing required parameter '{name}' in {location}")]
17 MissingRequiredParameter { name: String, location: String },
18
19 #[error("invalid parameter '{name}' in {location}: {reason}")]
20 InvalidParameter {
21 name: String,
22 location: String,
23 reason: String,
24 },
25
26 #[error("missing required request body")]
27 MissingRequiredBody,
28
29 #[error("unsupported content-type: {0}")]
30 UnsupportedContentType(String),
31
32 #[error("invalid request body: {0}")]
33 InvalidBody(String),
34
35 #[error("request body too large: {size} bytes exceeds limit of {limit} bytes")]
36 BodyTooLarge { size: usize, limit: usize },
37
38 #[error("too many headers: {count} exceeds limit of {limit}")]
39 TooManyHeaders { count: usize, limit: usize },
40
41 #[error("URI too long: {length} characters exceeds limit of {limit}")]
42 UriTooLong { length: usize, limit: usize },
43
44 #[error("header '{name}' too large: {size} bytes exceeds limit of {limit} bytes")]
45 HeaderTooLarge {
46 name: String,
47 size: usize,
48 limit: usize,
49 },
50}
51
52#[derive(Debug, Clone, serde::Serialize)]
54pub struct ProblemDetails {
55 #[serde(rename = "type")]
56 pub error_type: String,
57 pub title: String,
58 pub status: u16,
59 #[serde(skip_serializing_if = "Option::is_none")]
60 pub detail: Option<String>,
61 #[serde(skip_serializing_if = "Option::is_none")]
62 pub instance: Option<String>,
63 #[serde(flatten)]
65 pub extensions: HashMap<String, Value>,
66}
67
68impl ProblemDetails {
69 pub fn validation_error(errors: &[ValidationError2], dev_mode: bool) -> Self {
70 let mut extensions = HashMap::new();
71
72 if dev_mode && !errors.is_empty() {
73 let error_details: Vec<Value> = errors
74 .iter()
75 .map(|e| {
76 let mut detail = serde_json::Map::new();
77 match e {
78 ValidationError2::MissingRequiredParameter { name, location } => {
79 detail.insert("field".into(), Value::String(name.clone()));
80 detail.insert("location".into(), Value::String(location.clone()));
81 detail.insert(
82 "reason".into(),
83 Value::String("missing required parameter".into()),
84 );
85 }
86 ValidationError2::InvalidParameter {
87 name,
88 location,
89 reason,
90 } => {
91 detail.insert("field".into(), Value::String(name.clone()));
92 detail.insert("location".into(), Value::String(location.clone()));
93 detail.insert("reason".into(), Value::String(reason.clone()));
94 }
95 ValidationError2::MissingRequiredBody => {
96 detail.insert("field".into(), Value::String("body".into()));
97 detail.insert(
98 "reason".into(),
99 Value::String("missing required request body".into()),
100 );
101 }
102 ValidationError2::UnsupportedContentType(ct) => {
103 detail.insert("field".into(), Value::String("content-type".into()));
104 detail.insert(
105 "reason".into(),
106 Value::String(format!("unsupported: {}", ct)),
107 );
108 }
109 ValidationError2::InvalidBody(reason) => {
110 detail.insert("field".into(), Value::String("body".into()));
111 detail.insert("reason".into(), Value::String(reason.clone()));
112 }
113 ValidationError2::BodyTooLarge { size, limit } => {
114 detail.insert("field".into(), Value::String("body".into()));
115 detail.insert(
116 "reason".into(),
117 Value::String(format!(
118 "body too large: {} bytes exceeds {} byte limit",
119 size, limit
120 )),
121 );
122 }
123 ValidationError2::TooManyHeaders { count, limit } => {
124 detail.insert("field".into(), Value::String("headers".into()));
125 detail.insert(
126 "reason".into(),
127 Value::String(format!(
128 "too many headers: {} exceeds {} limit",
129 count, limit
130 )),
131 );
132 }
133 ValidationError2::UriTooLong { length, limit } => {
134 detail.insert("field".into(), Value::String("uri".into()));
135 detail.insert(
136 "reason".into(),
137 Value::String(format!(
138 "URI too long: {} chars exceeds {} char limit",
139 length, limit
140 )),
141 );
142 }
143 ValidationError2::HeaderTooLarge { name, size, limit } => {
144 detail
145 .insert("field".into(), Value::String(format!("header:{}", name)));
146 detail.insert(
147 "reason".into(),
148 Value::String(format!(
149 "header too large: {} bytes exceeds {} byte limit",
150 size, limit
151 )),
152 );
153 }
154 }
155 Value::Object(detail)
156 })
157 .collect();
158 extensions.insert("errors".into(), Value::Array(error_details));
159 }
160
161 let detail = if errors.len() == 1 {
162 Some(errors[0].to_string())
163 } else {
164 Some(format!("{} validation errors", errors.len()))
165 };
166
167 ProblemDetails {
168 error_type: "urn:barbacane:error:validation-failed".into(),
169 title: "Request validation failed".into(),
170 status: 400,
171 detail,
172 instance: None,
173 extensions,
174 }
175 }
176
177 pub fn to_json(&self) -> String {
178 serde_json::to_string(self).unwrap_or_else(|_| {
179 r#"{"type":"urn:barbacane:error:internal","title":"Serialization error","status":500}"#.into()
180 })
181 }
182}
183
184#[derive(Debug, Clone)]
186pub struct RequestLimits {
187 pub max_body_size: usize,
189 pub max_headers: usize,
191 pub max_header_size: usize,
193 pub max_uri_length: usize,
195}
196
197impl Default for RequestLimits {
198 fn default() -> Self {
199 Self {
200 max_body_size: 1024 * 1024, max_headers: 100,
202 max_header_size: 8 * 1024, max_uri_length: 8 * 1024, }
205 }
206}
207
208impl RequestLimits {
209 pub fn validate_uri(&self, uri: &str) -> Result<(), ValidationError2> {
211 if uri.len() > self.max_uri_length {
212 return Err(ValidationError2::UriTooLong {
213 length: uri.len(),
214 limit: self.max_uri_length,
215 });
216 }
217 Ok(())
218 }
219
220 pub fn validate_headers(
222 &self,
223 headers: &HashMap<String, String>,
224 ) -> Result<(), ValidationError2> {
225 if headers.len() > self.max_headers {
226 return Err(ValidationError2::TooManyHeaders {
227 count: headers.len(),
228 limit: self.max_headers,
229 });
230 }
231
232 for (name, value) in headers {
233 let header_size = name.len() + value.len();
234 if header_size > self.max_header_size {
235 return Err(ValidationError2::HeaderTooLarge {
236 name: name.clone(),
237 size: header_size,
238 limit: self.max_header_size,
239 });
240 }
241 }
242
243 Ok(())
244 }
245
246 pub fn validate_body_size(&self, body_len: usize) -> Result<(), ValidationError2> {
248 if body_len > self.max_body_size {
249 return Err(ValidationError2::BodyTooLarge {
250 size: body_len,
251 limit: self.max_body_size,
252 });
253 }
254 Ok(())
255 }
256
257 pub fn validate_all(
259 &self,
260 uri: &str,
261 headers: &HashMap<String, String>,
262 body_len: usize,
263 ) -> Result<(), Vec<ValidationError2>> {
264 let mut errors = Vec::new();
265
266 if let Err(e) = self.validate_uri(uri) {
267 errors.push(e);
268 }
269
270 if let Err(e) = self.validate_headers(headers) {
271 errors.push(e);
272 }
273
274 if let Err(e) = self.validate_body_size(body_len) {
275 errors.push(e);
276 }
277
278 if errors.is_empty() {
279 Ok(())
280 } else {
281 Err(errors)
282 }
283 }
284}
285
286fn compile_schema_with_formats(schema: &Value) -> Option<jsonschema::Validator> {
290 jsonschema::options()
291 .should_validate_formats(true)
292 .build(schema)
293 .ok()
294}
295
296pub struct OperationValidator {
298 path_params: Vec<CompiledParam>,
300 query_params: Vec<CompiledParam>,
302 header_params: Vec<CompiledParam>,
304 querystring_param: Option<CompiledParam>,
306 request_body: Option<CompiledRequestBody>,
308}
309
310struct CompiledParam {
311 name: String,
312 required: bool,
313 schema: Option<jsonschema::Validator>,
314}
315
316struct CompiledRequestBody {
317 required: bool,
318 content: HashMap<String, Option<jsonschema::Validator>>,
320}
321
322fn validate_params(
326 params: &[CompiledParam],
327 lookup: impl Fn(&str) -> Option<String>,
328 location: &str,
329) -> Result<(), Vec<ValidationError2>> {
330 let mut errors = Vec::new();
331
332 for param in params {
333 match lookup(¶m.name) {
334 Some(value) => {
335 if let Some(schema) = ¶m.schema {
336 let json_value = Value::String(value);
337 let validation_errors: Vec<_> = schema.iter_errors(&json_value).collect();
338 if !validation_errors.is_empty() {
339 let reasons: Vec<String> =
340 validation_errors.iter().map(|e| e.to_string()).collect();
341 errors.push(ValidationError2::InvalidParameter {
342 name: param.name.clone(),
343 location: location.into(),
344 reason: reasons.join("; "),
345 });
346 }
347 }
348 }
349 None if param.required => {
350 errors.push(ValidationError2::MissingRequiredParameter {
351 name: param.name.clone(),
352 location: location.into(),
353 });
354 }
355 None => {}
356 }
357 }
358
359 if errors.is_empty() {
360 Ok(())
361 } else {
362 Err(errors)
363 }
364}
365
366impl OperationValidator {
367 pub fn new(parameters: &[Parameter], request_body: Option<&RequestBody>) -> Self {
369 let mut path_params = Vec::new();
370 let mut query_params = Vec::new();
371 let mut header_params = Vec::new();
372 let mut querystring_param = None;
373
374 for param in parameters {
375 let compiled = CompiledParam {
376 name: param.name.clone(),
377 required: param.required || param.location == "path", schema: param.schema.as_ref().and_then(compile_schema_with_formats),
379 };
380
381 match param.location.as_str() {
382 "path" => path_params.push(compiled),
383 "query" => query_params.push(compiled),
384 "header" => header_params.push(compiled),
385 "querystring" => querystring_param = Some(compiled),
386 _ => {} }
388 }
389
390 let compiled_body = request_body.map(|rb| {
391 let mut content = HashMap::new();
392 for (media_type, content_schema) in &rb.content {
393 let schema = content_schema
394 .schema
395 .as_ref()
396 .and_then(compile_schema_with_formats);
397 content.insert(media_type.clone(), schema);
398 }
399 CompiledRequestBody {
400 required: rb.required,
401 content,
402 }
403 });
404
405 Self {
406 path_params,
407 query_params,
408 header_params,
409 querystring_param,
410 request_body: compiled_body,
411 }
412 }
413
414 pub fn validate_path_params(
416 &self,
417 params: &[(String, String)],
418 ) -> Result<(), Vec<ValidationError2>> {
419 let param_map: HashMap<_, _> = params.iter().cloned().collect();
420 validate_params(
421 &self.path_params,
422 |name| param_map.get(name).cloned(),
423 "path",
424 )
425 }
426
427 pub fn validate_query_params(
429 &self,
430 query_string: Option<&str>,
431 ) -> Result<(), Vec<ValidationError2>> {
432 let param_map: HashMap<String, String> = query_string
433 .unwrap_or("")
434 .split('&')
435 .filter(|s| !s.is_empty())
436 .filter_map(|pair| {
437 let mut parts = pair.splitn(2, '=');
438 let key = parts.next()?;
439 let value = parts.next().unwrap_or("");
440 Some((urlencoding_decode(key), urlencoding_decode(value)))
441 })
442 .collect();
443
444 validate_params(
445 &self.query_params,
446 |name| param_map.get(name).cloned(),
447 "query",
448 )
449 }
450
451 pub fn validate_querystring(
453 &self,
454 query_string: Option<&str>,
455 ) -> Result<(), Vec<ValidationError2>> {
456 let Some(param) = &self.querystring_param else {
457 return Ok(());
458 };
459
460 let qs = query_string.unwrap_or("");
461
462 if qs.is_empty() && param.required {
463 return Err(vec![ValidationError2::MissingRequiredParameter {
464 name: param.name.clone(),
465 location: "querystring".into(),
466 }]);
467 }
468
469 if !qs.is_empty() {
470 if let Some(schema) = ¶m.schema {
471 let json_value = Value::String(qs.to_string());
472 let validation_errors: Vec<_> = schema.iter_errors(&json_value).collect();
473 if !validation_errors.is_empty() {
474 let reasons: Vec<String> =
475 validation_errors.iter().map(|e| e.to_string()).collect();
476 return Err(vec![ValidationError2::InvalidParameter {
477 name: param.name.clone(),
478 location: "querystring".into(),
479 reason: reasons.join("; "),
480 }]);
481 }
482 }
483 }
484
485 Ok(())
486 }
487
488 pub fn validate_headers(
490 &self,
491 headers: &HashMap<String, String>,
492 ) -> Result<(), Vec<ValidationError2>> {
493 let headers_lower: HashMap<String, String> = headers
494 .iter()
495 .map(|(k, v)| (k.to_lowercase(), v.clone()))
496 .collect();
497
498 validate_params(
499 &self.header_params,
500 |name| headers_lower.get(&name.to_lowercase()).cloned(),
501 "header",
502 )
503 }
504
505 pub fn validate_body(
507 &self,
508 content_type: Option<&str>,
509 body: &[u8],
510 ) -> Result<(), Vec<ValidationError2>> {
511 let Some(body_spec) = &self.request_body else {
512 return Ok(());
514 };
515
516 if body_spec.required && body.is_empty() {
518 return Err(vec![ValidationError2::MissingRequiredBody]);
519 }
520
521 if body.is_empty() {
523 return Ok(());
524 }
525
526 let ct = content_type.unwrap_or("application/octet-stream");
528 let base_ct = ct.split(';').next().unwrap_or(ct).trim();
529
530 let schema = if let Some(schema) = body_spec.content.get(base_ct) {
532 schema
533 } else if let Some(schema) = body_spec.content.get("*/*") {
534 schema
535 } else {
536 return Err(vec![ValidationError2::UnsupportedContentType(
537 base_ct.to_string(),
538 )]);
539 };
540
541 if let Some(schema) = schema {
543 if base_ct.contains("json") {
544 let json_body: Value = match serde_json::from_slice(body) {
545 Ok(v) => v,
546 Err(e) => {
547 return Err(vec![ValidationError2::InvalidBody(format!(
548 "invalid JSON: {}",
549 e
550 ))]);
551 }
552 };
553
554 let validation_errors: Vec<_> = schema.iter_errors(&json_body).collect();
555 if !validation_errors.is_empty() {
556 let reasons: Vec<String> =
557 validation_errors.iter().map(|e| e.to_string()).collect();
558 return Err(vec![ValidationError2::InvalidBody(reasons.join("; "))]);
559 }
560 }
561 }
562
563 Ok(())
564 }
565
566 pub fn validate_request(
568 &self,
569 path_params: &[(String, String)],
570 query_string: Option<&str>,
571 headers: &HashMap<String, String>,
572 content_type: Option<&str>,
573 body: &[u8],
574 ) -> Result<(), Vec<ValidationError2>> {
575 self.validate_path_params(path_params)?;
577 self.validate_query_params(query_string)?;
578 self.validate_querystring(query_string)?;
579 self.validate_headers(headers)?;
580 self.validate_body(content_type, body)?;
581 Ok(())
582 }
583}
584
585fn urlencoding_decode(input: &str) -> String {
587 let mut result = String::with_capacity(input.len());
588 let mut chars = input.chars().peekable();
589
590 while let Some(c) = chars.next() {
591 if c == '%' {
592 let hex: String = chars.by_ref().take(2).collect();
593 if let Ok(byte) = u8::from_str_radix(&hex, 16) {
594 result.push(byte as char);
595 } else {
596 result.push('%');
597 result.push_str(&hex);
598 }
599 } else if c == '+' {
600 result.push(' ');
601 } else {
602 result.push(c);
603 }
604 }
605
606 result
607}
608
609#[cfg(test)]
610mod tests {
611 use super::compile_schema_with_formats;
612 use super::*;
613
614 fn make_param(name: &str, location: &str, required: bool, schema: Option<Value>) -> Parameter {
615 Parameter {
616 name: name.to_string(),
617 location: location.to_string(),
618 required,
619 schema,
620 }
621 }
622
623 #[test]
624 fn validate_required_path_param() {
625 let params = vec![make_param("id", "path", true, None)];
626 let validator = OperationValidator::new(¶ms, None);
627
628 let result = validator.validate_path_params(&[]);
630 assert!(result.is_err());
631
632 let result = validator.validate_path_params(&[("id".into(), "123".into())]);
634 assert!(result.is_ok());
635 }
636
637 #[test]
638 fn validate_path_param_schema() {
639 let schema = serde_json::json!({
641 "type": "string",
642 "pattern": "^[0-9]+$"
643 });
644 let params = vec![make_param("id", "path", true, Some(schema))];
645 let validator = OperationValidator::new(¶ms, None);
646
647 let result = validator.validate_path_params(&[("id".into(), "123".into())]);
649 assert!(result.is_ok());
650
651 let result = validator.validate_path_params(&[("id".into(), "abc".into())]);
653 assert!(result.is_err());
654 }
655
656 #[test]
657 fn validate_required_query_param() {
658 let params = vec![make_param("page", "query", true, None)];
659 let validator = OperationValidator::new(¶ms, None);
660
661 let result = validator.validate_query_params(Some(""));
663 assert!(result.is_err());
664
665 let result = validator.validate_query_params(Some("page=1"));
667 assert!(result.is_ok());
668 }
669
670 #[test]
671 fn validate_optional_query_param() {
672 let params = vec![make_param("limit", "query", false, None)];
673 let validator = OperationValidator::new(¶ms, None);
674
675 let result = validator.validate_query_params(Some(""));
677 assert!(result.is_ok());
678 }
679
680 #[test]
681 fn validate_required_body() {
682 use barbacane_compiler::ContentSchema;
683 use std::collections::BTreeMap;
684
685 let mut content = BTreeMap::new();
686 content.insert(
687 "application/json".to_string(),
688 ContentSchema { schema: None },
689 );
690
691 let request_body = RequestBody {
692 required: true,
693 content,
694 };
695
696 let validator = OperationValidator::new(&[], Some(&request_body));
697
698 let result = validator.validate_body(Some("application/json"), &[]);
700 assert!(result.is_err());
701
702 let result = validator.validate_body(Some("application/json"), b"{}");
704 assert!(result.is_ok());
705 }
706
707 #[test]
708 fn validate_body_schema() {
709 use barbacane_compiler::ContentSchema;
710 use std::collections::BTreeMap;
711
712 let schema = serde_json::json!({
713 "type": "object",
714 "required": ["name"],
715 "properties": {
716 "name": { "type": "string" }
717 }
718 });
719
720 let mut content = BTreeMap::new();
721 content.insert(
722 "application/json".to_string(),
723 ContentSchema {
724 schema: Some(schema),
725 },
726 );
727
728 let request_body = RequestBody {
729 required: true,
730 content,
731 };
732
733 let validator = OperationValidator::new(&[], Some(&request_body));
734
735 let result = validator.validate_body(Some("application/json"), br#"{"name":"test"}"#);
737 assert!(result.is_ok());
738
739 let result = validator.validate_body(Some("application/json"), b"{}");
741 assert!(result.is_err());
742 }
743
744 #[test]
745 fn validate_unsupported_content_type() {
746 use barbacane_compiler::ContentSchema;
747 use std::collections::BTreeMap;
748
749 let mut content = BTreeMap::new();
750 content.insert(
751 "application/json".to_string(),
752 ContentSchema { schema: None },
753 );
754
755 let request_body = RequestBody {
756 required: true,
757 content,
758 };
759
760 let validator = OperationValidator::new(&[], Some(&request_body));
761
762 let result = validator.validate_body(Some("text/plain"), b"hello");
763 assert!(result.is_err());
764
765 if let Err(errors) = result {
766 assert!(matches!(
767 errors[0],
768 ValidationError2::UnsupportedContentType(_)
769 ));
770 }
771 }
772
773 #[test]
774 fn problem_details_format() {
775 let errors = vec![ValidationError2::MissingRequiredParameter {
776 name: "id".into(),
777 location: "path".into(),
778 }];
779
780 let problem = ProblemDetails::validation_error(&errors, false);
781 assert_eq!(problem.status, 400);
782 assert_eq!(problem.error_type, "urn:barbacane:error:validation-failed");
783
784 let json = problem.to_json();
785 assert!(json.contains("validation-failed"));
786 }
787
788 #[test]
789 fn problem_details_dev_mode() {
790 let errors = vec![ValidationError2::MissingRequiredParameter {
791 name: "id".into(),
792 location: "path".into(),
793 }];
794
795 let problem = ProblemDetails::validation_error(&errors, true);
796 let json = problem.to_json();
797
798 assert!(json.contains("errors"));
800 assert!(json.contains("field"));
801 }
802
803 #[test]
808 fn validate_uri_length_ok() {
809 let limits = RequestLimits::default();
810 let uri = "/api/users/123";
811 assert!(limits.validate_uri(uri).is_ok());
812 }
813
814 #[test]
815 fn validate_uri_length_too_long() {
816 let limits = RequestLimits {
817 max_uri_length: 10,
818 ..Default::default()
819 };
820 let uri = "/api/users/123456789";
821 let result = limits.validate_uri(uri);
822 assert!(result.is_err());
823 assert!(matches!(
824 result.unwrap_err(),
825 ValidationError2::UriTooLong { .. }
826 ));
827 }
828
829 #[test]
830 fn validate_header_count_ok() {
831 let limits = RequestLimits::default();
832 let headers: HashMap<String, String> = (0..10)
833 .map(|i| (format!("Header-{}", i), "value".to_string()))
834 .collect();
835 assert!(limits.validate_headers(&headers).is_ok());
836 }
837
838 #[test]
839 fn validate_header_count_too_many() {
840 let limits = RequestLimits {
841 max_headers: 5,
842 ..Default::default()
843 };
844 let headers: HashMap<String, String> = (0..10)
845 .map(|i| (format!("Header-{}", i), "value".to_string()))
846 .collect();
847 let result = limits.validate_headers(&headers);
848 assert!(result.is_err());
849 assert!(matches!(
850 result.unwrap_err(),
851 ValidationError2::TooManyHeaders { .. }
852 ));
853 }
854
855 #[test]
856 fn validate_header_size_ok() {
857 let limits = RequestLimits::default();
858 let mut headers = HashMap::new();
859 headers.insert("Content-Type".to_string(), "application/json".to_string());
860 assert!(limits.validate_headers(&headers).is_ok());
861 }
862
863 #[test]
864 fn validate_header_size_too_large() {
865 let limits = RequestLimits {
866 max_header_size: 20,
867 ..Default::default()
868 };
869 let mut headers = HashMap::new();
870 headers.insert("X-Very-Long-Header".to_string(), "a".repeat(100));
871 let result = limits.validate_headers(&headers);
872 assert!(result.is_err());
873 assert!(matches!(
874 result.unwrap_err(),
875 ValidationError2::HeaderTooLarge { .. }
876 ));
877 }
878
879 #[test]
880 fn validate_body_size_ok() {
881 let limits = RequestLimits::default();
882 assert!(limits.validate_body_size(1000).is_ok());
883 }
884
885 #[test]
886 fn validate_body_size_too_large() {
887 let limits = RequestLimits {
888 max_body_size: 100,
889 ..Default::default()
890 };
891 let result = limits.validate_body_size(1000);
892 assert!(result.is_err());
893 assert!(matches!(
894 result.unwrap_err(),
895 ValidationError2::BodyTooLarge { .. }
896 ));
897 }
898
899 #[test]
904 fn format_validation_email() {
905 let schema = serde_json::json!({
906 "type": "object",
907 "properties": {
908 "email": { "type": "string", "format": "email" }
909 }
910 });
911 let validator = compile_schema_with_formats(&schema).unwrap();
912
913 let valid = serde_json::json!({"email": "user@example.com"});
915 assert!(validator.is_valid(&valid));
916
917 let invalid = serde_json::json!({"email": "not-an-email"});
919 assert!(!validator.is_valid(&invalid));
920 }
921
922 #[test]
923 fn format_validation_uuid() {
924 let schema = serde_json::json!({
925 "type": "string",
926 "format": "uuid"
927 });
928 let validator = compile_schema_with_formats(&schema).unwrap();
929
930 let valid = serde_json::json!("550e8400-e29b-41d4-a716-446655440000");
932 assert!(validator.is_valid(&valid));
933
934 let invalid = serde_json::json!("not-a-uuid");
936 assert!(!validator.is_valid(&invalid));
937 }
938
939 #[test]
940 fn format_validation_date_time() {
941 let schema = serde_json::json!({
942 "type": "string",
943 "format": "date-time"
944 });
945 let validator = compile_schema_with_formats(&schema).unwrap();
946
947 let valid = serde_json::json!("2024-01-29T12:30:00Z");
949 assert!(validator.is_valid(&valid));
950
951 let invalid = serde_json::json!("not-a-date");
953 assert!(!validator.is_valid(&invalid));
954 }
955
956 #[test]
957 fn format_validation_uri() {
958 let schema = serde_json::json!({
959 "type": "string",
960 "format": "uri"
961 });
962 let validator = compile_schema_with_formats(&schema).unwrap();
963
964 let valid = serde_json::json!("https://example.com/path?query=1");
966 assert!(validator.is_valid(&valid));
967
968 let invalid = serde_json::json!("not a uri");
970 assert!(!validator.is_valid(&invalid));
971 }
972
973 #[test]
974 fn format_validation_ipv4() {
975 let schema = serde_json::json!({
976 "type": "string",
977 "format": "ipv4"
978 });
979 let validator = compile_schema_with_formats(&schema).unwrap();
980
981 let valid = serde_json::json!("192.168.1.1");
983 assert!(validator.is_valid(&valid));
984
985 let invalid = serde_json::json!("999.999.999.999");
987 assert!(!validator.is_valid(&invalid));
988 }
989
990 #[test]
991 fn format_validation_ipv6() {
992 let schema = serde_json::json!({
993 "type": "string",
994 "format": "ipv6"
995 });
996 let validator = compile_schema_with_formats(&schema).unwrap();
997
998 let valid = serde_json::json!("2001:0db8:85a3:0000:0000:8a2e:0370:7334");
1000 assert!(validator.is_valid(&valid));
1001
1002 let invalid = serde_json::json!("not-ipv6");
1004 assert!(!validator.is_valid(&invalid));
1005 }
1006
1007 #[test]
1012 fn validate_all_limits() {
1013 let limits = RequestLimits {
1014 max_uri_length: 10,
1015 max_headers: 2,
1016 max_body_size: 50,
1017 ..Default::default()
1018 };
1019
1020 let mut headers = HashMap::new();
1021 headers.insert("A".to_string(), "1".to_string());
1022
1023 assert!(limits.validate_all("/short", &headers, 10).is_ok());
1025
1026 let result = limits.validate_all("/this/is/a/very/long/uri", &headers, 10);
1028 assert!(result.is_err());
1029 assert_eq!(result.unwrap_err().len(), 1);
1030
1031 let many_headers: HashMap<String, String> = (0..5)
1033 .map(|i| (format!("H{}", i), "v".to_string()))
1034 .collect();
1035 let result = limits.validate_all("/this/is/a/very/long/uri", &many_headers, 100);
1036 assert!(result.is_err());
1037 assert_eq!(result.unwrap_err().len(), 3); }
1039
1040 #[test]
1045 fn validate_asyncapi_message_payload() {
1046 use barbacane_compiler::ContentSchema;
1048 use std::collections::BTreeMap;
1049
1050 let message_schema = serde_json::json!({
1052 "type": "object",
1053 "required": ["eventId", "userId", "timestamp"],
1054 "properties": {
1055 "eventId": { "type": "string", "format": "uuid" },
1056 "userId": { "type": "string" },
1057 "timestamp": { "type": "string", "format": "date-time" },
1058 "metadata": {
1059 "type": "object",
1060 "additionalProperties": true
1061 }
1062 }
1063 });
1064
1065 let mut content = BTreeMap::new();
1066 content.insert(
1067 "application/json".to_string(),
1068 ContentSchema {
1069 schema: Some(message_schema),
1070 },
1071 );
1072
1073 let request_body = RequestBody {
1074 required: true,
1075 content,
1076 };
1077
1078 let validator = OperationValidator::new(&[], Some(&request_body));
1079
1080 let valid_payload = br#"{
1082 "eventId": "550e8400-e29b-41d4-a716-446655440000",
1083 "userId": "user-123",
1084 "timestamp": "2024-01-29T12:30:00Z"
1085 }"#;
1086 let result = validator.validate_body(Some("application/json"), valid_payload);
1087 assert!(result.is_ok(), "Valid message should pass: {:?}", result);
1088
1089 let missing_field = br#"{
1091 "eventId": "550e8400-e29b-41d4-a716-446655440000",
1092 "userId": "user-123"
1093 }"#;
1094 let result = validator.validate_body(Some("application/json"), missing_field);
1095 assert!(result.is_err(), "Missing timestamp should fail");
1096
1097 let wrong_format = br#"{
1099 "eventId": "not-a-uuid",
1100 "userId": "user-123",
1101 "timestamp": "2024-01-29T12:30:00Z"
1102 }"#;
1103 let result = validator.validate_body(Some("application/json"), wrong_format);
1104 assert!(result.is_err(), "Invalid UUID format should fail");
1105 }
1106
1107 #[test]
1108 fn validate_asyncapi_message_with_avro_content_type() {
1109 use barbacane_compiler::ContentSchema;
1111 use std::collections::BTreeMap;
1112
1113 let message_schema = serde_json::json!({
1114 "type": "object",
1115 "required": ["key"],
1116 "properties": {
1117 "key": { "type": "string" }
1118 }
1119 });
1120
1121 let mut content = BTreeMap::new();
1122 content.insert(
1123 "application/vnd.apache.avro+json".to_string(),
1124 ContentSchema {
1125 schema: Some(message_schema),
1126 },
1127 );
1128
1129 let request_body = RequestBody {
1130 required: true,
1131 content,
1132 };
1133
1134 let validator = OperationValidator::new(&[], Some(&request_body));
1135
1136 let result = validator.validate_body(
1138 Some("application/vnd.apache.avro+json"),
1139 br#"{"key": "value"}"#,
1140 );
1141 assert!(result.is_ok());
1142
1143 let result =
1145 validator.validate_body(Some("application/octet-stream"), br#"{"key": "value"}"#);
1146 assert!(result.is_err());
1147 }
1148
1149 #[test]
1154 fn validate_required_header_param() {
1155 let params = vec![make_param("X-Request-Id", "header", true, None)];
1156 let validator = OperationValidator::new(¶ms, None);
1157
1158 let headers = HashMap::new();
1160 let result = validator.validate_headers(&headers);
1161 assert!(result.is_err());
1162 let errors = result.unwrap_err();
1163 assert!(matches!(
1164 &errors[0],
1165 ValidationError2::MissingRequiredParameter { name, location }
1166 if name == "X-Request-Id" && location == "header"
1167 ));
1168
1169 let mut headers = HashMap::new();
1171 headers.insert("X-Request-Id".to_string(), "abc-123".to_string());
1172 let result = validator.validate_headers(&headers);
1173 assert!(result.is_ok());
1174 }
1175
1176 #[test]
1177 fn validate_optional_header_param() {
1178 let params = vec![make_param("X-Trace-Id", "header", false, None)];
1179 let validator = OperationValidator::new(¶ms, None);
1180
1181 let headers = HashMap::new();
1183 let result = validator.validate_headers(&headers);
1184 assert!(result.is_ok());
1185 }
1186
1187 #[test]
1188 fn validate_header_param_schema() {
1189 let schema = serde_json::json!({
1190 "type": "string",
1191 "pattern": "^Bearer .+$"
1192 });
1193 let params = vec![make_param("Authorization", "header", true, Some(schema))];
1194 let validator = OperationValidator::new(¶ms, None);
1195
1196 let mut headers = HashMap::new();
1198 headers.insert("Authorization".to_string(), "Bearer token123".to_string());
1199 let result = validator.validate_headers(&headers);
1200 assert!(result.is_ok());
1201
1202 headers.insert(
1204 "Authorization".to_string(),
1205 "Basic dXNlcjpwYXNz".to_string(),
1206 );
1207 let result = validator.validate_headers(&headers);
1208 assert!(result.is_err());
1209 let errors = result.unwrap_err();
1210 assert!(matches!(
1211 &errors[0],
1212 ValidationError2::InvalidParameter { name, location, .. }
1213 if name == "Authorization" && location == "header"
1214 ));
1215 }
1216
1217 #[test]
1218 fn validate_header_param_case_insensitive() {
1219 let params = vec![make_param("X-Request-Id", "header", true, None)];
1220 let validator = OperationValidator::new(¶ms, None);
1221
1222 let mut headers = HashMap::new();
1224 headers.insert("x-request-id".to_string(), "abc-123".to_string());
1225 let result = validator.validate_headers(&headers);
1226 assert!(result.is_ok());
1227
1228 let mut headers = HashMap::new();
1230 headers.insert("X-REQUEST-ID".to_string(), "abc-123".to_string());
1231 let result = validator.validate_headers(&headers);
1232 assert!(result.is_ok());
1233 }
1234
1235 #[test]
1236 fn validate_asyncapi_channel_parameter() {
1237 let schema = serde_json::json!({
1239 "type": "string",
1240 "pattern": "^user-[a-z0-9]+$"
1241 });
1242
1243 let params = vec![make_param("userId", "path", true, Some(schema))];
1244 let validator = OperationValidator::new(¶ms, None);
1245
1246 let result = validator.validate_path_params(&[("userId".into(), "user-abc123".into())]);
1248 assert!(result.is_ok());
1249
1250 let result = validator.validate_path_params(&[("userId".into(), "invalid".into())]);
1252 assert!(result.is_err());
1253 }
1254
1255 #[test]
1260 fn validate_querystring_param_present() {
1261 let schema = serde_json::json!({
1262 "type": "string",
1263 "minLength": 1
1264 });
1265 let params = vec![make_param("q", "querystring", true, Some(schema))];
1266 let validator = OperationValidator::new(¶ms, None);
1267
1268 let result = validator.validate_querystring(Some("filter=active&sort=name"));
1270 assert!(result.is_ok());
1271 }
1272
1273 #[test]
1274 fn validate_querystring_param_missing_required() {
1275 let params = vec![make_param("q", "querystring", true, None)];
1276 let validator = OperationValidator::new(¶ms, None);
1277
1278 let result = validator.validate_querystring(Some(""));
1280 assert!(result.is_err());
1281 let errors = result.unwrap_err();
1282 assert!(matches!(
1283 &errors[0],
1284 ValidationError2::MissingRequiredParameter { location, .. }
1285 if location == "querystring"
1286 ));
1287 }
1288
1289 #[test]
1290 fn validate_querystring_param_schema() {
1291 let schema = serde_json::json!({
1292 "type": "string",
1293 "pattern": "^[a-z]+=\\w+$"
1294 });
1295 let params = vec![make_param("q", "querystring", false, Some(schema))];
1296 let validator = OperationValidator::new(¶ms, None);
1297
1298 let result = validator.validate_querystring(Some("key=value"));
1300 assert!(result.is_ok());
1301
1302 let result = validator.validate_querystring(Some("KEY=value&other=123"));
1304 assert!(result.is_err());
1305 }
1306
1307 #[test]
1308 fn validate_querystring_not_set() {
1309 let params = vec![make_param("name", "query", false, None)];
1311 let validator = OperationValidator::new(¶ms, None);
1312
1313 let result = validator.validate_querystring(Some("anything"));
1314 assert!(result.is_ok());
1315 }
1316}