1 use prost::{DecodeError, Message};
2 use prost_types::Any;
3 
4 use crate::richer_error::FromAnyRef;
5 
6 use super::super::{pb, FromAny, IntoAny};
7 
8 /// Used at the `field_violations` field of the [`BadRequest`] struct.
9 /// Describes a single bad request field.
10 #[derive(Clone, Debug)]
11 pub struct FieldViolation {
12     /// Path leading to a field in the request body. Value should be a
13     /// sequence of dot-separated identifiers that identify a protocol buffer
14     /// field.
15     pub field: String,
16 
17     /// Description of why the field is bad.
18     pub description: String,
19 }
20 
21 impl FieldViolation {
22     /// Creates a new [`FieldViolation`] struct.
new(field: impl Into<String>, description: impl Into<String>) -> Self23     pub fn new(field: impl Into<String>, description: impl Into<String>) -> Self {
24         FieldViolation {
25             field: field.into(),
26             description: description.into(),
27         }
28     }
29 }
30 
31 impl From<pb::bad_request::FieldViolation> for FieldViolation {
from(value: pb::bad_request::FieldViolation) -> Self32     fn from(value: pb::bad_request::FieldViolation) -> Self {
33         FieldViolation {
34             field: value.field,
35             description: value.description,
36         }
37     }
38 }
39 
40 impl From<FieldViolation> for pb::bad_request::FieldViolation {
from(value: FieldViolation) -> Self41     fn from(value: FieldViolation) -> Self {
42         pb::bad_request::FieldViolation {
43             field: value.field,
44             description: value.description,
45         }
46     }
47 }
48 
49 /// Used to encode/decode the `BadRequest` standard error message described in
50 /// [error_details.proto]. Describes violations in a client request. Focuses
51 /// on the syntactic aspects of the request.
52 ///
53 /// [error_details.proto]: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
54 #[derive(Clone, Debug)]
55 pub struct BadRequest {
56     /// Describes all field violations of the request.
57     pub field_violations: Vec<FieldViolation>,
58 }
59 
60 impl BadRequest {
61     /// Type URL of the `BadRequest` standard error message type.
62     pub const TYPE_URL: &'static str = "type.googleapis.com/google.rpc.BadRequest";
63 
64     /// Creates a new [`BadRequest`] struct.
new(field_violations: impl Into<Vec<FieldViolation>>) -> Self65     pub fn new(field_violations: impl Into<Vec<FieldViolation>>) -> Self {
66         BadRequest {
67             field_violations: field_violations.into(),
68         }
69     }
70 
71     /// Creates a new [`BadRequest`] struct with a single [`FieldViolation`] in
72     /// `field_violations`.
with_violation(field: impl Into<String>, description: impl Into<String>) -> Self73     pub fn with_violation(field: impl Into<String>, description: impl Into<String>) -> Self {
74         BadRequest {
75             field_violations: vec![FieldViolation {
76                 field: field.into(),
77                 description: description.into(),
78             }],
79         }
80     }
81 
82     /// Adds a [`FieldViolation`] to [`BadRequest`]'s `field_violations`.
add_violation( &mut self, field: impl Into<String>, description: impl Into<String>, ) -> &mut Self83     pub fn add_violation(
84         &mut self,
85         field: impl Into<String>,
86         description: impl Into<String>,
87     ) -> &mut Self {
88         self.field_violations.append(&mut vec![FieldViolation {
89             field: field.into(),
90             description: description.into(),
91         }]);
92         self
93     }
94 
95     /// Returns `true` if [`BadRequest`]'s `field_violations` vector is empty,
96     /// and `false` if it is not.
is_empty(&self) -> bool97     pub fn is_empty(&self) -> bool {
98         self.field_violations.is_empty()
99     }
100 }
101 
102 impl IntoAny for BadRequest {
into_any(self) -> Any103     fn into_any(self) -> Any {
104         let detail_data: pb::BadRequest = self.into();
105 
106         Any {
107             type_url: BadRequest::TYPE_URL.to_string(),
108             value: detail_data.encode_to_vec(),
109         }
110     }
111 }
112 
113 impl FromAny for BadRequest {
114     #[inline]
from_any(any: Any) -> Result<Self, DecodeError>115     fn from_any(any: Any) -> Result<Self, DecodeError> {
116         FromAnyRef::from_any_ref(&any)
117     }
118 }
119 
120 impl FromAnyRef for BadRequest {
from_any_ref(any: &Any) -> Result<Self, DecodeError>121     fn from_any_ref(any: &Any) -> Result<Self, DecodeError> {
122         let buf: &[u8] = &any.value;
123         let bad_req = pb::BadRequest::decode(buf)?;
124 
125         Ok(bad_req.into())
126     }
127 }
128 
129 impl From<pb::BadRequest> for BadRequest {
from(value: pb::BadRequest) -> Self130     fn from(value: pb::BadRequest) -> Self {
131         BadRequest {
132             field_violations: value.field_violations.into_iter().map(Into::into).collect(),
133         }
134     }
135 }
136 
137 impl From<BadRequest> for pb::BadRequest {
from(value: BadRequest) -> Self138     fn from(value: BadRequest) -> Self {
139         pb::BadRequest {
140             field_violations: value.field_violations.into_iter().map(Into::into).collect(),
141         }
142     }
143 }
144 
145 #[cfg(test)]
146 mod tests {
147     use super::super::super::{FromAny, IntoAny};
148     use super::BadRequest;
149 
150     #[test]
gen_bad_request()151     fn gen_bad_request() {
152         let mut br_details = BadRequest::new(Vec::new());
153         let formatted = format!("{:?}", br_details);
154 
155         let expected = "BadRequest { field_violations: [] }";
156 
157         assert!(
158             formatted.eq(expected),
159             "empty BadRequest differs from expected result"
160         );
161 
162         assert!(
163             br_details.is_empty(),
164             "empty BadRequest returns 'false' from .is_empty()"
165         );
166 
167         br_details
168             .add_violation("field_a", "description_a")
169             .add_violation("field_b", "description_b");
170 
171         let formatted = format!("{:?}", br_details);
172 
173         let expected_filled = "BadRequest { field_violations: [FieldViolation { field: \"field_a\", description: \"description_a\" }, FieldViolation { field: \"field_b\", description: \"description_b\" }] }";
174 
175         assert!(
176             formatted.eq(expected_filled),
177             "filled BadRequest differs from expected result"
178         );
179 
180         assert!(
181             !br_details.is_empty(),
182             "filled BadRequest returns 'true' from .is_empty()"
183         );
184 
185         let gen_any = br_details.into_any();
186         let formatted = format!("{:?}", gen_any);
187 
188         let expected = "Any { type_url: \"type.googleapis.com/google.rpc.BadRequest\", value: [10, 24, 10, 7, 102, 105, 101, 108, 100, 95, 97, 18, 13, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 95, 97, 10, 24, 10, 7, 102, 105, 101, 108, 100, 95, 98, 18, 13, 100, 101, 115, 99, 114, 105, 112, 116, 105, 111, 110, 95, 98] }";
189 
190         assert!(
191             formatted.eq(expected),
192             "Any from filled BadRequest differs from expected result"
193         );
194 
195         let br_details = match BadRequest::from_any(gen_any) {
196             Err(error) => panic!("Error generating BadRequest from Any: {:?}", error),
197             Ok(from_any) => from_any,
198         };
199 
200         let formatted = format!("{:?}", br_details);
201 
202         assert!(
203             formatted.eq(expected_filled),
204             "BadRequest from Any differs from expected result"
205         );
206     }
207 }
208