tonic_richer_error/std_messages/
prec_failure.rs

1use prost::{DecodeError, Message};
2use prost_types::Any;
3
4use super::super::pb;
5use super::super::{FromAny, IntoAny};
6
7/// Used at the `violations` field of the [`PreconditionFailure`] struct.
8/// Describes a single precondition failure.
9#[derive(Clone, Debug)]
10pub struct PreconditionViolation {
11    /// Type of the PreconditionFailure. At [error_details.proto], the usage
12    /// of a service-specific enum type is recommended. For example, "TOS" for
13    /// a "Terms of Service" violation.
14    ///
15    /// [error_details.proto]: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
16    pub r#type: String,
17
18    /// Subject, relative to the type, that failed.
19    pub subject: String,
20
21    /// A description of how the precondition failed.
22    pub description: String,
23}
24
25impl PreconditionViolation {
26    /// Creates a new [`PreconditionViolation`] struct.
27    pub fn new(
28        r#type: impl Into<String>,
29        subject: impl Into<String>,
30        description: impl Into<String>,
31    ) -> Self {
32        PreconditionViolation {
33            r#type: r#type.into(),
34            subject: subject.into(),
35            description: description.into(),
36        }
37    }
38}
39
40/// Used to encode/decode the `PreconditionFailure` standard error message
41/// described in [error_details.proto]. Describes what preconditions have
42/// failed.
43///
44/// [error_details.proto]: https://github.com/googleapis/googleapis/blob/master/google/rpc/error_details.proto
45#[derive(Clone, Debug)]
46pub struct PreconditionFailure {
47    /// Describes all precondition violations of the request.
48    pub violations: Vec<PreconditionViolation>,
49}
50
51impl PreconditionFailure {
52    /// Type URL of the `PreconditionFailure` standard error message type.
53    pub const TYPE_URL: &'static str = "type.googleapis.com/google.rpc.PreconditionFailure";
54
55    /// Creates a new [`PreconditionFailure`] struct.
56    pub fn new(violations: Vec<PreconditionViolation>) -> Self {
57        PreconditionFailure {
58            violations: violations,
59        }
60    }
61
62    /// Creates a new [`PreconditionFailure`] struct with a single
63    /// [`PreconditionViolation`] in `violations`.
64    pub fn with_violation(
65        violation_type: impl Into<String>,
66        subject: impl Into<String>,
67        description: impl Into<String>,
68    ) -> Self {
69        PreconditionFailure {
70            violations: vec![PreconditionViolation {
71                r#type: violation_type.into(),
72                subject: subject.into(),
73                description: description.into(),
74            }],
75        }
76    }
77}
78
79impl PreconditionFailure {
80    /// Adds a [`PreconditionViolation`] to [`PreconditionFailure`]'s
81    /// `violations` vector.
82    pub fn add_violation(
83        &mut self,
84        r#type: impl Into<String>,
85        subject: impl Into<String>,
86        description: impl Into<String>,
87    ) -> &mut Self {
88        self.violations.append(&mut vec![PreconditionViolation {
89            r#type: r#type.into(),
90            subject: subject.into(),
91            description: description.into(),
92        }]);
93        self
94    }
95
96    /// Returns `true` if [`PreconditionFailure`]'s `violations` vector is
97    /// empty, and `false` if it is not.
98    pub fn is_empty(&self) -> bool {
99        self.violations.is_empty()
100    }
101}
102
103impl IntoAny for PreconditionFailure {
104    fn into_any(self) -> Any {
105        let detail_data = pb::PreconditionFailure {
106            violations: self
107                .violations
108                .into_iter()
109                .map(|v| pb::precondition_failure::Violation {
110                    r#type: v.r#type,
111                    subject: v.subject,
112                    description: v.description,
113                })
114                .collect(),
115        };
116
117        Any {
118            type_url: PreconditionFailure::TYPE_URL.to_string(),
119            value: detail_data.encode_to_vec(),
120        }
121    }
122}
123
124impl FromAny for PreconditionFailure {
125    fn from_any(any: Any) -> Result<Self, DecodeError> {
126        let buf: &[u8] = &any.value;
127        let precondition_failure = pb::PreconditionFailure::decode(buf)?;
128
129        let precondition_failure = PreconditionFailure {
130            violations: precondition_failure
131                .violations
132                .into_iter()
133                .map(|v| PreconditionViolation {
134                    r#type: v.r#type,
135                    subject: v.subject,
136                    description: v.description,
137                })
138                .collect(),
139        };
140
141        Ok(precondition_failure)
142    }
143}
144
145#[cfg(test)]
146mod tests {
147
148    use super::super::super::{FromAny, IntoAny};
149    use super::PreconditionFailure;
150
151    #[test]
152    fn gen_prec_failure() {
153        let mut prec_failure = PreconditionFailure::new(Vec::new());
154        let formatted = format!("{:?}", prec_failure);
155
156        println!("empty PreconditionFailure -> {formatted}");
157
158        let expected = "PreconditionFailure { violations: [] }";
159
160        assert!(
161            formatted.eq(expected),
162            "empty PreconditionFailure differs from expected result"
163        );
164
165        assert!(
166            prec_failure.is_empty(),
167            "empty PreconditionFailure returns 'false' from .is_empty()"
168        );
169
170        prec_failure
171            .add_violation("TOS", "example.local", "Terms of service not accepted")
172            .add_violation("FNF", "example.local", "File not found");
173
174        let formatted = format!("{:?}", prec_failure);
175
176        println!("filled PreconditionFailure -> {formatted}");
177
178        let expected_filled = "PreconditionFailure { violations: [PreconditionViolation { type: \"TOS\", subject: \"example.local\", description: \"Terms of service not accepted\" }, PreconditionViolation { type: \"FNF\", subject: \"example.local\", description: \"File not found\" }] }";
179
180        assert!(
181            formatted.eq(expected_filled),
182            "filled PreconditionFailure differs from expected result"
183        );
184
185        assert!(
186            prec_failure.is_empty() == false,
187            "filled PreconditionFailure returns 'true' from .is_empty()"
188        );
189
190        let gen_any = prec_failure.into_any();
191
192        let formatted = format!("{:?}", gen_any);
193
194        println!("Any generated from PreconditionFailure -> {formatted}");
195
196        let expected = "Any { type_url: \"type.googleapis.com/google.rpc.PreconditionFailure\", value: [10, 51, 10, 3, 84, 79, 83, 18, 13, 101, 120, 97, 109, 112, 108, 101, 46, 108, 111, 99, 97, 108, 26, 29, 84, 101, 114, 109, 115, 32, 111, 102, 32, 115, 101, 114, 118, 105, 99, 101, 32, 110, 111, 116, 32, 97, 99, 99, 101, 112, 116, 101, 100, 10, 36, 10, 3, 70, 78, 70, 18, 13, 101, 120, 97, 109, 112, 108, 101, 46, 108, 111, 99, 97, 108, 26, 14, 70, 105, 108, 101, 32, 110, 111, 116, 32, 102, 111, 117, 110, 100] }";
197
198        assert!(
199            formatted.eq(expected),
200            "Any from filled PreconditionFailure differs from expected result"
201        );
202
203        let br_details = match PreconditionFailure::from_any(gen_any) {
204            Err(error) => panic!("Error generating PreconditionFailure from Any: {:?}", error),
205            Ok(from_any) => from_any,
206        };
207
208        let formatted = format!("{:?}", br_details);
209
210        println!("PreconditionFailure generated from Any -> {formatted}");
211
212        assert!(
213            formatted.eq(expected_filled),
214            "PreconditionFailure from Any differs from expected result"
215        );
216    }
217}