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 ..tonic_types::FieldViolation::default()
38 }
39 }
40}
41
42#[cfg(feature = "tonic")]
43impl From<Error> for tonic_types::ErrorDetails {
44 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 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_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}