1use crate::errors;
2
3#[derive(Debug, Clone)]
5pub struct Error {
6 pub field: String,
8 pub details: errors::Error,
10}
11
12impl Error {
13 #[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 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 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 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 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_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 use crate::errors::message;
101 use crate::Error;
102
103 #[test]
104 fn test_format_err() {
105 let err = format_err!("field", "something wrong");
106 assert_eq!(
107 err.to_string(),
108 "\"field\": invalid validation rules: something wrong"
109 );
110
111 let err = format_err!("field", "something wrong: {}", "value");
112 assert_eq!(
113 err.to_string(),
114 "\"field\": invalid validation rules: something wrong: value"
115 );
116
117 let field = "field";
118 let value = "value";
119 let err = format_err!(field, "something wrong: {value}");
120 assert_eq!(
121 err.to_string(),
122 "\"field\": invalid validation rules: something wrong: value"
123 );
124 }
125
126 #[cfg(feature = "tonic")]
127 #[test]
128 fn test_status() {
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}