thin_status/
thin_status.rs1#[cfg(feature = "use_cloud_rpc")]
16use google_cloud_rpc::model as cloud_rpc;
17use std::error::Error;
18use std::num::NonZeroI32;
19
20use crate::any_details::any::Details;
21use crate::builder;
22use crate::status_code;
23use crate::thin_arc_or_int::{IsizeInPtr, ThinArcOrInt};
24
25#[derive(Clone, Debug, PartialEq)]
28#[cfg_attr(not(feature = "use_any"), derive(Copy, Eq, PartialOrd, Ord, Hash))]
29pub(crate) struct FullStatus {
30 pub(crate) code: NonZeroI32,
31 pub(crate) details: Details,
32}
33
34impl TryFrom<FullStatus> for IsizeInPtr {
40 type Error = FullStatus;
41
42 fn try_from(value: FullStatus) -> Result<IsizeInPtr, FullStatus> {
43 if value.details.is_empty() {
44 value.code.get().try_into().map_err(|_| value)
45 } else {
46 Err(value)
47 }
48 }
49}
50
51#[derive(Clone, Debug, PartialEq)]
62#[cfg_attr(not(feature = "use_any"), derive(Eq, PartialOrd, Ord, Hash))]
63pub struct ThinStatus {
64 thin: ThinArcOrInt<FullStatus, u8>,
67}
68
69impl ThinStatus {
70 pub fn builder<'a, C: Into<NonZeroI32>>(code: C) -> builder::ThinStatusBuilder<'a> {
72 builder::ThinStatusBuilder::new(code)
73 }
74
75 pub(crate) fn from_builder(builder: builder::ThinStatusBuilder) -> Self {
79 ThinStatus {
80 thin: ThinArcOrInt::from_convertible(builder.full, builder.message.as_bytes()),
81 }
82 }
83
84 pub fn from_code<C: Into<NonZeroI32>>(code: C) -> Self {
87 ThinStatus {
88 thin: ThinArcOrInt::from_convertible(
89 FullStatus {
90 code: code.into(),
91 details: Default::default(),
92 },
93 &[],
94 ),
95 }
96 }
97
98 pub fn code(&self) -> Option<status_code::ErrorCode> {
101 self.code_raw().get().try_into().ok()
102 }
103
104 pub fn code_or_unknown(&self) -> status_code::ErrorCode {
106 self.code().unwrap_or(status_code::ErrorCode::Unknown)
107 }
108
109 pub fn code_raw(&self) -> NonZeroI32 {
111 match self.thin.as_isize() {
112 Some(code) => unsafe { NonZeroI32::new_unchecked(code as i32) },
113 None => {
114 let val = &self.thin.as_arc();
115 let val = val.expect("ThinArcOrInt contains neither code nor arc");
116 val.header.header.code
117 }
118 }
119 }
120
121 pub fn message(&self) -> &str {
123 match self.thin.as_arc() {
124 None => "",
125 Some(arc) => unsafe { str::from_utf8_unchecked(&arc.slice) },
126 }
127 }
128
129 #[cfg(feature = "use_any")]
131 pub fn details(&self) -> &[google_cloud_wkt::Any] {
132 let &arc = &self.thin.as_arc();
133 arc.map_or::<&[google_cloud_wkt::Any], _>(&[], |t| &t.header.header.details.get())
134 }
135
136 #[cfg(feature = "use_cloud_rpc")]
138 pub fn to_cloud_rpc(&self) -> cloud_rpc::Status {
139 self.into()
140 }
141
142 #[cfg(feature = "use_cloud_rpc")]
144 pub fn try_from_cloud_rpc(status: cloud_rpc::Status) -> Result<Self, ()> {
145 status.try_into()
146 }
147
148 #[cfg(feature = "use_cloud_rpc")]
151 pub fn try_from_cloud_rpc_ref(status: &cloud_rpc::Status) -> Result<Self, ()> {
152 status.try_into()
153 }
154}
155
156impl std::fmt::Display for ThinStatus {
157 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
158 let code = self.code_raw().get();
159 if let Some(error_code) = status_code::ErrorCode::try_from(code).ok() {
160 error_code.fmt(f)?;
161 } else {
162 code.fmt(f)?;
163 }
164 let msg = self.message();
165 if !msg.is_empty() {
166 write!(f, ": {}", msg)?;
167 }
168 #[cfg(feature = "use_any")]
169 {
170 if let Some(t) = &self.thin.as_arc() {
171 t.header.header.details.fmt(f)?;
172 }
173 }
174 Ok(())
175 }
176}
177
178impl Error for ThinStatus {}
179
180impl From<status_code::ErrorCode> for ThinStatus {
181 fn from(code: status_code::ErrorCode) -> Self {
182 Self::from_code(NonZeroI32::from(code))
183 }
184}
185
186#[cfg(feature = "use_cloud_rpc")]
188impl TryFrom<cloud_rpc::Status> for ThinStatus {
189 type Error = ();
190
191 fn try_from(status: cloud_rpc::Status) -> Result<Self, ()> {
192 let code: NonZeroI32 = status.code.try_into().map_err(|_| ())?;
193 Ok(Self::builder(code)
194 .message(&status.message)
195 .details(status.details)
196 .build())
197 }
198}
199
200#[cfg(feature = "use_cloud_rpc")]
203impl TryFrom<&cloud_rpc::Status> for ThinStatus {
204 type Error = ();
205
206 fn try_from(status: &cloud_rpc::Status) -> Result<Self, ()> {
207 let code: NonZeroI32 = status.code.try_into().map_err(|_| ())?;
208 Ok(Self::builder(code)
209 .message(&status.message)
210 .details(status.details.clone())
211 .build())
212 }
213}
214
215#[cfg(feature = "use_cloud_rpc")]
217impl From<&ThinStatus> for cloud_rpc::Status {
218 fn from(status: &ThinStatus) -> Self {
219 let mut result = Self::new();
220 result.code = status.code_raw().get();
221 result.message = status.message().to_string();
222 result.details = status.details().to_vec();
223 result
224 }
225}
226
227#[cfg(test)]
228mod thin_status_tests {
229 use super::*;
230 use std::fmt::Write;
231
232 const MAX_THIN: NonZeroI32 = NonZeroI32::new(if IsizeInPtr::MAX as u64 <= i32::MAX as u64 {
235 IsizeInPtr::MAX as i32
236 } else {
237 i32::MAX
238 })
239 .unwrap();
240
241 const fn non_zero(value: i32) -> NonZeroI32 {
242 NonZeroI32::new(value).unwrap()
243 }
244
245 #[test]
246 fn test_size_optimization() {
247 assert_eq!(
249 std::mem::size_of::<ThinStatus>(),
250 std::mem::size_of::<usize>()
251 );
252 }
253
254 #[test]
255 fn test_from_error_code_raw() {
256 let status: ThinStatus = status_code::ErrorCode::NotFound.into();
258 assert_eq!(<NonZeroI32 as Into<i32>>::into(status.code_raw()), 5);
259 assert_eq!(status.message(), "");
260 #[cfg(feature = "use_any")]
261 assert_eq!(status.details(), &[]);
262 assert!(status.thin.has_number());
263 assert_eq!(format!("{}", status), "NOT_FOUND");
264 }
265
266 #[test]
267 fn test_from_code_within_max_thin() {
268 let status_pos = ThinStatus::from_code(MAX_THIN);
270 assert_eq!(status_pos.code_raw(), MAX_THIN);
271 assert_eq!(status_pos.message(), "");
272 assert!(status_pos.thin.has_number());
273
274 let status_neg = ThinStatus::from_code(-MAX_THIN);
275 assert_eq!(status_neg.code_raw(), -MAX_THIN);
276 assert_eq!(
277 status_neg.code_or_unknown(),
278 status_code::ErrorCode::Unknown
279 );
280 assert_eq!(status_neg.message(), "");
281 assert!(status_neg.thin.has_number());
282
283 let status_normal = ThinStatus::from_code(non_zero(42));
284 assert_eq!(status_normal.code_raw(), non_zero(42));
285 assert_eq!(status_normal.message(), "");
286 assert!(status_normal.thin.has_number());
287 }
288
289 #[test]
291 fn test_from_code_outside_max_thin() {
292 if MAX_THIN.get() < NonZeroI32::MAX.into() {
295 let larger: NonZeroI32 = non_zero(MAX_THIN.get() + 1);
296 let status_overflow = ThinStatus::from_code(larger);
297 assert_eq!(status_overflow.code_raw(), larger);
298 assert_eq!(status_overflow.message(), "");
299
300 let status_underflow = ThinStatus::from_code(-larger);
301 assert_eq!(status_underflow.code_raw(), -larger);
302 assert_eq!(status_underflow.message(), "");
303 assert!(status_overflow.thin.has_ref());
304 assert!(status_underflow.thin.has_ref());
305 }
306
307 let status_max = ThinStatus::from_code(NonZeroI32::MAX);
309 assert_eq!(status_max.code_raw(), NonZeroI32::MAX);
310 let status_min = ThinStatus::from_code(NonZeroI32::MIN);
311 assert_eq!(status_min.code_raw(), NonZeroI32::MIN);
312 if MAX_THIN.get() < NonZeroI32::MAX.into() {
313 assert!(status_max.thin.has_ref());
314 assert!(status_min.thin.has_ref());
315 }
316 }
317
318 #[test]
319 fn test_from_error_code_and_message() {
320 let mut builder = ThinStatus::builder(status_code::ErrorCode::NotFound);
321 write!(builder, "message").expect("write! failed");
322 let status = builder.build();
323 assert_eq!(<NonZeroI32 as Into<i32>>::into(status.code_raw()), 5);
324 assert_eq!(status.code(), Some(status_code::ErrorCode::NotFound));
325 assert_eq!(status.code_or_unknown(), status_code::ErrorCode::NotFound);
326 assert_eq!(status.message(), "message");
327 #[cfg(feature = "use_any")]
328 assert_eq!(status.details(), &[]);
329 assert!(status.thin.has_ref());
330 assert_eq!(format!("{}", status), "NOT_FOUND: message");
331 }
332
333 #[cfg(feature = "use_any")]
334 #[test]
335 fn test_with_details() {
336 let detail =
337 google_cloud_wkt::Any::from_msg(&google_cloud_wkt::Duration::clamp(123, 456)).unwrap();
338 let status = ThinStatus::builder(status_code::ErrorCode::NotFound)
339 .add_detail(detail.clone())
340 .build();
341 assert_eq!(<NonZeroI32 as Into<i32>>::into(status.code_raw()), 5);
342 assert_eq!(status.message(), "");
343 assert_eq!(status.details(), vec![detail]);
344 assert!(status.thin.has_ref());
345 let formatted = format!("{}", status);
346 assert!(formatted.starts_with("NOT_FOUND [Any("));
347 assert!(formatted.contains("type.googleapis.com/google.protobuf.Duration"));
348 assert!(formatted.contains("123"));
349 }
350
351 #[test]
352 fn test_clone_and_equality() {
353 let status1 = ThinStatus::builder(non_zero(13))
354 .message("Permission Denied")
355 .build();
356 let status2 = status1.clone();
357
358 assert_eq!(status1, status2);
359 assert_eq!(status1.code_raw(), status2.code_raw());
360 assert_eq!(status1.message(), status2.message());
361
362 let _status_different = ThinStatus::from_code(non_zero(13));
363 assert_ne!(status1, _status_different);
364 }
365
366 #[cfg(feature = "use_cloud_rpc")]
367 #[test]
368 fn test_cloud_rpc_status_conversions() {
369 assert!(
370 ThinStatus::try_from(cloud_rpc::Status::new()).is_err(),
371 "OK status shouldn't be convertible to ThinStatus"
372 );
373
374 let detail =
375 google_cloud_wkt::Any::from_msg(&google_cloud_wkt::Duration::clamp(123, 456)).unwrap();
376 let status = ThinStatus::builder(status_code::ErrorCode::NotFound)
377 .message("Be yourself! Everyone else is already taken.")
378 .add_detail(detail)
379 .build();
380 assert_eq!(
381 ThinStatus::try_from(cloud_rpc::Status::from(&status)).as_ref(),
382 Ok(&status)
383 );
384
385 assert_eq!(
386 ThinStatus::try_from(&cloud_rpc::Status::from(&status)),
387 Ok(status)
388 );
389 }
390}