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            ..tonic_types::FieldViolation::default()
38        }
39    }
40}
41
42#[cfg(feature = "tonic")]
43impl From<Error> for tonic_types::ErrorDetails {
44    /// Converts an `Error` into `ErrorDetails`.
45    fn from(value: Error) -> Self {
46        tonic_types::ErrorDetails::with_bad_request(vec![value.into()])
47    }
48}
49
50#[cfg(feature = "tonic")]
51impl From<Error> for tonic::Status {
52    /// Converts an `Error` into a `tonic::Status`.
53    fn from(value: Error) -> Self {
54        let code = match value.details {
55            errors::Error::InvalidRules(_) => tonic::Code::Internal,
56            _ => tonic::Code::InvalidArgument,
57        };
58        <tonic::Status as tonic_types::StatusExt>::with_error_details(
59            code,
60            value.to_string(),
61            value.into(),
62        )
63    }
64}
65
66/// Macro to format an error.
67///
68/// # Arguments
69///
70/// * `$msg` - The error message.
71/// * `$field` - The field associated with the error.
72/// * `$arg` - Additional arguments for the error message.
73///
74/// # Returns
75///
76/// An `Error` instance.
77#[macro_export]
78macro_rules! format_err {
79    ($msg:literal $(,)?) => {
80        ::prost_validate::Error {
81            field: "".to_string(),
82            details: ::prost_validate::errors::Error::InvalidRules(format!("{}", $msg)),
83        }
84    };
85    ($field:ident, $msg:ident) => {
86        ::prost_validate::Error {
87            field: format!("{}", $field),
88            details: ::prost_validate::errors::Error::InvalidRules(format!("{}", $msg)),
89        }
90    };
91    ($field:expr, $($arg:tt)*) => {
92        ::prost_validate::Error {
93            field: format!("{}", $field),
94            details: ::prost_validate::errors::Error::InvalidRules(format!($($arg)*)),
95        }
96    };
97}
98
99#[cfg(test)]
100mod tests {
101    #[test]
102    fn test_format_err() {
103        let err = format_err!("field", "something wrong");
104        assert_eq!(
105            err.to_string(),
106            "\"field\": invalid validation rules: something wrong"
107        );
108
109        let err = format_err!("field", "something wrong: {}", "value");
110        assert_eq!(
111            err.to_string(),
112            "\"field\": invalid validation rules: something wrong: value"
113        );
114
115        let field = "field";
116        let value = "value";
117        let err = format_err!(field, "something wrong: {value}");
118        assert_eq!(
119            err.to_string(),
120            "\"field\": invalid validation rules: something wrong: value"
121        );
122    }
123
124    #[cfg(feature = "tonic")]
125    #[test]
126    #[allow(clippy::unwrap_used)]
127    fn test_status() {
128        use crate::errors::message;
129        use crate::Error;
130        use tonic_types::StatusExt;
131
132        let status: tonic::Status = Error::new("field", message::Error::Required).into();
133        assert_eq!(status.code(), tonic::Code::InvalidArgument);
134        let details = status.get_error_details();
135        assert!(details.bad_request().is_some());
136        let f = &details.bad_request().unwrap().field_violations;
137        assert_eq!(f.len(), 1);
138        assert_eq!(f[0].field, "field");
139        assert_eq!(f[0].description, "required");
140    }
141}