axum_valid/
lib.rs

1#![doc = include_str!("../README.md")]
2#![deny(unsafe_code, missing_docs, clippy::unwrap_used)]
3
4#[cfg(feature = "extra")]
5pub mod extra;
6#[cfg(feature = "form")]
7pub mod form;
8#[cfg(feature = "garde")]
9pub mod garde;
10#[cfg(feature = "json")]
11pub mod json;
12#[cfg(feature = "msgpack")]
13pub mod msgpack;
14pub mod path;
15#[cfg(feature = "query")]
16pub mod query;
17#[cfg(feature = "typed_header")]
18pub mod typed_header;
19#[cfg(feature = "validator")]
20pub mod validator;
21#[cfg(feature = "validify")]
22pub mod validify;
23#[cfg(feature = "yaml")]
24pub mod yaml;
25
26#[cfg(feature = "cbor")]
27pub mod cbor;
28#[cfg(feature = "sonic")]
29pub mod sonic;
30#[cfg(feature = "toml")]
31pub mod toml;
32#[cfg(feature = "typed_multipart")]
33pub mod typed_multipart;
34#[cfg(feature = "xml")]
35pub mod xml;
36
37use axum::http::StatusCode;
38use axum::response::{IntoResponse, Response};
39use std::error::Error;
40use std::fmt::Display;
41
42/// Http status code returned when there are validation errors.
43#[cfg(feature = "422")]
44pub const VALIDATION_ERROR_STATUS: StatusCode = StatusCode::UNPROCESSABLE_ENTITY;
45/// Http status code returned when there are validation errors.
46#[cfg(not(feature = "422"))]
47pub const VALIDATION_ERROR_STATUS: StatusCode = StatusCode::BAD_REQUEST;
48
49/// Trait for types that can supply a reference that can be validated.
50///
51/// Extractor types `T` that implement this trait can be used with `Valid`, `Garde` or `Validated`.
52///
53pub trait HasValidate {
54    /// Inner type that can be validated for correctness
55    type Validate;
56    /// Get the inner value
57    fn get_validate(&self) -> &Self::Validate;
58}
59
60#[cfg(feature = "validator")]
61pub use crate::validator::{HasValidateArgs, Valid, ValidEx, ValidRejection};
62
63#[cfg(feature = "garde")]
64pub use crate::garde::{Garde, GardeRejection};
65
66#[cfg(feature = "validify")]
67pub use crate::validify::{
68    HasModify, HasValidify, Modified, PayloadExtractor, Validated, Validified, ValidifiedByRef,
69    ValidifyRejection,
70};
71
72/// `ValidationRejection` is returned when the validation extractor fails.
73///
74/// This enumeration captures two types of errors that can occur when using `Valid`: errors related to the validation
75/// extractor itself , and errors that may arise within the inner extractor (represented by `Inner`).
76///
77#[derive(Debug)]
78pub enum ValidationRejection<V, E> {
79    /// `Valid` variant captures errors related to the validation logic.
80    Valid(V),
81    /// `Inner` variant represents potential errors that might occur within the inner extractor.
82    Inner(E),
83}
84
85impl<V: Display, E: Display> Display for ValidationRejection<V, E> {
86    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
87        match self {
88            ValidationRejection::Valid(errors) => write!(f, "{errors}"),
89            ValidationRejection::Inner(error) => write!(f, "{error}"),
90        }
91    }
92}
93
94impl<V: Error + 'static, E: Error + 'static> Error for ValidationRejection<V, E> {
95    fn source(&self) -> Option<&(dyn Error + 'static)> {
96        match self {
97            ValidationRejection::Valid(ve) => Some(ve),
98            ValidationRejection::Inner(e) => Some(e),
99        }
100    }
101}
102
103#[cfg(feature = "into_json")]
104impl<V: serde::Serialize, E: IntoResponse> IntoResponse for ValidationRejection<V, E> {
105    fn into_response(self) -> Response {
106        match self {
107            ValidationRejection::Valid(v) => {
108                (VALIDATION_ERROR_STATUS, axum::Json(v)).into_response()
109            }
110            ValidationRejection::Inner(e) => e.into_response(),
111        }
112    }
113}
114
115#[cfg(not(feature = "into_json"))]
116impl<V: Display, E: IntoResponse> IntoResponse for ValidationRejection<V, E> {
117    fn into_response(self) -> Response {
118        match self {
119            ValidationRejection::Valid(v) => {
120                (VALIDATION_ERROR_STATUS, v.to_string()).into_response()
121            }
122            ValidationRejection::Inner(e) => e.into_response(),
123        }
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use axum::http::StatusCode;
130    use reqwest::RequestBuilder;
131
132    /// # Valid test parameter
133    pub trait ValidTestParameter: 'static {
134        /// Create a valid parameter
135        fn valid() -> &'static Self;
136        /// Create an error serializable array
137        fn error() -> &'static [(&'static str, &'static str)];
138        /// Create a invalid parameter
139        fn invalid() -> &'static Self;
140    }
141
142    /// # Valid Tests
143    ///
144    /// This trait defines three test cases to check
145    /// if an extractor combined with the Valid type works properly.
146    ///
147    /// 1. For a valid request, the server should return `200 OK`.
148    /// 2. For an invalid request according to the extractor, the server should return the error HTTP status code defined by the extractor itself.
149    /// 3. For an invalid request according to Valid, the server should return VALIDATION_ERROR_STATUS as the error code.
150    ///
151    pub trait ValidTest {
152        /// The HTTP status code returned when inner extractor failed.
153        const ERROR_STATUS_CODE: StatusCode;
154        /// The HTTP status code returned when the outer extractor fails.
155        /// Use crate::VALIDATION_ERROR_STATUS by default.
156        const INVALID_STATUS_CODE: StatusCode = crate::VALIDATION_ERROR_STATUS;
157        /// If the response body can be serialized into JSON format
158        const JSON_SERIALIZABLE: bool = true;
159        /// Build a valid request, the server should return `200 OK`.
160        fn set_valid_request(builder: RequestBuilder) -> RequestBuilder;
161        /// Build an invalid request according to the extractor, the server should return `Self::ERROR_STATUS_CODE`
162        fn set_error_request(builder: RequestBuilder) -> RequestBuilder;
163        /// Build an invalid request according to Valid, the server should return VALIDATION_ERROR_STATUS
164        fn set_invalid_request(builder: RequestBuilder) -> RequestBuilder;
165    }
166
167    #[cfg(feature = "extra")]
168    pub trait Rejection {
169        const STATUS_CODE: StatusCode;
170    }
171}