prost_validate/
error.rs

1use crate::errors;
2
3/// Represents a validation error for a field.
4#[derive(Debug, Clone)]
5pub struct Error {
6    /// The field associated with the error.
7    pub field: String,
8    /// The error message.
9    pub details: errors::Error,
10}
11
12impl Error {
13    /// Creates a new `Error` instance.
14    #[allow(clippy::needless_pass_by_value)]
15    pub fn new<T: ToString>(field: T, details: impl Into<errors::Error>) -> Self {
16        Self {
17            field: field.to_string(),
18            details: details.into(),
19        }
20    }
21}
22
23impl std::fmt::Display for Error {
24    /// Formats the error for display.
25    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
26        write!(f, "\"{}\": {}", self.field, self.details)
27    }
28}
29
30#[cfg(feature = "tonic")]
31impl From<Error> for tonic_types::FieldViolation {
32    /// Converts an `Error` into a `FieldViolation`.
33    fn from(value: Error) -> Self {
34        Self {
35            field: value.field,
36            description: value.details.to_string(),
37        }
38    }
39}
40
41#[cfg(feature = "tonic")]
42impl From<Error> for tonic_types::ErrorDetails {
43    /// Converts an `Error` into `ErrorDetails`.
44    fn from(value: Error) -> Self {
45        tonic_types::ErrorDetails::with_bad_request(vec![value.into()])
46    }
47}
48
49#[cfg(feature = "tonic")]
50impl From<Error> for tonic::Status {
51    /// Converts an `Error` into a `tonic::Status`.
52    fn from(value: Error) -> Self {
53        let code = match value.details {
54            errors::Error::InvalidRules(_) => tonic::Code::Internal,
55            _ => tonic::Code::InvalidArgument,
56        };
57        <tonic::Status as tonic_types::StatusExt>::with_error_details(
58            code,
59            value.to_string(),
60            value.into(),
61        )
62    }
63}
64
65/// Macro to format an error.
66///
67/// # Arguments
68///
69/// * `$msg` - The error message.
70/// * `$field` - The field associated with the error.
71/// * `$arg` - Additional arguments for the error message.
72///
73/// # Returns
74///
75/// An `Error` instance.
76#[macro_export]
77macro_rules! format_err {
78    ($msg:literal $(,)?) => {
79        ::prost_validate::Error {
80            field: "".to_string(),
81            details: ::prost_validate::errors::Error::InvalidRules(format!("{}", $msg)),
82        }
83    };
84    ($field:ident, $msg:ident) => {
85        ::prost_validate::Error {
86            field: format!("{}", $field),
87            details: ::prost_validate::errors::Error::InvalidRules(format!("{}", $msg)),
88        }
89    };
90    ($field:expr, $($arg:tt)*) => {
91        ::prost_validate::Error {
92            field: format!("{}", $field),
93            details: ::prost_validate::errors::Error::InvalidRules(format!($($arg)*)),
94        }
95    };
96}
97
98#[cfg(test)]
99mod tests {
100    #[test]
101    fn test_format_err() {
102        let err = format_err!("field", "something wrong");
103        assert_eq!(
104            err.to_string(),
105            "\"field\": invalid validation rules: something wrong"
106        );
107
108        let err = format_err!("field", "something wrong: {}", "value");
109        assert_eq!(
110            err.to_string(),
111            "\"field\": invalid validation rules: something wrong: value"
112        );
113
114        let field = "field";
115        let value = "value";
116        let err = format_err!(field, "something wrong: {value}");
117        assert_eq!(
118            err.to_string(),
119            "\"field\": invalid validation rules: something wrong: value"
120        );
121    }
122
123    #[cfg(feature = "tonic")]
124    #[test]
125    #[allow(clippy::unwrap_used)]
126    fn test_status() {
127        use crate::errors::message;
128        use crate::Error;
129        use tonic_types::StatusExt;
130
131        let status: tonic::Status = Error::new("field", message::Error::Required).into();
132        assert_eq!(status.code(), tonic::Code::InvalidArgument);
133        let details = status.get_error_details();
134        assert!(details.bad_request().is_some());
135        let f = &details.bad_request().unwrap().field_violations;
136        assert_eq!(f.len(), 1);
137        assert_eq!(f[0].field, "field");
138        assert_eq!(f[0].description, "required");
139    }
140}