1#[cfg(feature = "use_cloud_rpc")]
16use google_cloud_rpc::model as cloud_rpc;
17use std::error::Error;
18use std::fmt::{Display, Write};
19use std::num::NonZeroI32;
20
21use crate::any_details::any::Details;
22use crate::builder;
23use crate::status_code;
24use crate::thin_arc_or_int::{IsizeInPtr, ThinArcOrInt};
25
26type Result<T, Error = ThinStatus> = std::result::Result<T, Error>;
27
28#[derive(Clone, Debug, PartialEq)]
31#[cfg_attr(not(feature = "use_any"), derive(Copy, Eq, PartialOrd, Ord, Hash))]
32pub(crate) struct FullStatus {
33 pub(crate) code: NonZeroI32,
34 pub(crate) details: Details,
35}
36
37impl TryFrom<FullStatus> for IsizeInPtr {
43 type Error = FullStatus;
44
45 fn try_from(value: FullStatus) -> Result<IsizeInPtr, FullStatus> {
46 if value.details.is_empty() {
47 value.code.get().try_into().map_err(|_| value)
48 } else {
49 Err(value)
50 }
51 }
52}
53
54#[derive(Clone, Debug, PartialEq)]
65#[cfg_attr(not(feature = "use_any"), derive(Eq, PartialOrd, Ord, Hash))]
66pub struct ThinStatus {
67 thin: ThinArcOrInt<FullStatus, u8>,
70}
71
72impl ThinStatus {
73 pub fn builder<'a, C: Into<NonZeroI32>>(code: C) -> builder::ThinStatusBuilder<'a> {
75 builder::ThinStatusBuilder::new(code)
76 }
77
78 pub(crate) fn from_builder(builder: builder::ThinStatusBuilder) -> Self {
82 ThinStatus {
83 thin: ThinArcOrInt::from_convertible(builder.full, builder.message.as_bytes()),
84 }
85 }
86
87 pub fn from_code<C: Into<NonZeroI32>>(code: C) -> Self {
90 ThinStatus {
91 thin: ThinArcOrInt::from_convertible(
92 FullStatus {
93 code: code.into(),
94 details: Default::default(),
95 },
96 &[],
97 ),
98 }
99 }
100
101 pub fn code(&self) -> Option<status_code::ErrorCode> {
104 self.code_raw().get().try_into().ok()
105 }
106
107 pub fn code_or_unknown(&self) -> status_code::ErrorCode {
109 self.code().unwrap_or(status_code::ErrorCode::Unknown)
110 }
111
112 pub fn code_raw(&self) -> NonZeroI32 {
114 match self.thin.as_isize() {
115 Some(code) => unsafe { NonZeroI32::new_unchecked(code as i32) },
116 None => {
117 let val = &self.thin.as_arc();
118 let val = val.expect("ThinArcOrInt contains neither code nor arc");
119 val.header.header.code
120 }
121 }
122 }
123
124 pub fn message(&self) -> &str {
126 match self.thin.as_arc() {
127 None => "",
128 Some(arc) => unsafe { str::from_utf8_unchecked(&arc.slice) },
129 }
130 }
131
132 #[cfg(feature = "use_any")]
134 pub fn details(&self) -> &[google_cloud_wkt::Any] {
135 let &arc = &self.thin.as_arc();
136 arc.map_or::<&[google_cloud_wkt::Any], _>(&[], |t| &t.header.header.details.get())
137 }
138
139 #[cfg(feature = "use_cloud_rpc")]
141 pub fn to_cloud_rpc(&self) -> cloud_rpc::Status {
142 self.into()
143 }
144
145 #[cfg(feature = "use_cloud_rpc")]
147 pub fn try_from_cloud_rpc(status: cloud_rpc::Status) -> Result<Self, ()> {
148 status.try_into()
149 }
150
151 #[cfg(feature = "use_cloud_rpc")]
154 pub fn try_from_cloud_rpc_ref(status: &cloud_rpc::Status) -> Result<Self, ()> {
155 status.try_into()
156 }
157}
158
159impl Display for ThinStatus {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165 let code = self.code_raw().get();
166 if let Some(error_code) = status_code::ErrorCode::try_from(code).ok() {
167 error_code.fmt(f)?;
168 } else {
169 code.fmt(f)?;
170 }
171 let msg = self.message();
172 if !msg.is_empty() {
173 write!(f, ": {}", msg)?;
174 }
175 Ok(())
176 }
177}
178
179impl Error for ThinStatus {}
180
181impl From<status_code::ErrorCode> for ThinStatus {
182 fn from(code: status_code::ErrorCode) -> Self {
183 Self::from_code(NonZeroI32::from(code))
184 }
185}
186
187impl From<std::io::Error> for ThinStatus {
188 fn from(err: std::io::Error) -> Self {
189 Self::from(&err)
190 }
191}
192
193impl From<&std::io::Error> for ThinStatus {
194 fn from(err: &std::io::Error) -> Self {
195 let mut builder = Self::builder(status_code::ErrorCode::from_error_kind(err.kind()));
196 let _ = write!(builder, "{}", err);
197 builder.build()
198 }
199}
200
201#[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)
211 .build())
212 }
213}
214
215#[cfg(feature = "use_cloud_rpc")]
218impl TryFrom<&cloud_rpc::Status> for ThinStatus {
219 type Error = ();
220
221 fn try_from(status: &cloud_rpc::Status) -> Result<Self, ()> {
222 let code: NonZeroI32 = status.code.try_into().map_err(|_| ())?;
223 Ok(Self::builder(code)
224 .message(&status.message)
225 .details(status.details.clone())
226 .build())
227 }
228}
229
230#[cfg(feature = "use_cloud_rpc")]
232impl From<&ThinStatus> for cloud_rpc::Status {
233 fn from(status: &ThinStatus) -> Self {
234 let mut result = Self::new();
235 result.code = status.code_raw().get();
236 result.message = status.message().to_string();
237 result.details = status.details().to_vec();
238 result
239 }
240}
241
242#[cfg(test)]
243mod thin_status_tests {
244 use super::*;
245 use std::fmt::Write;
246
247 const MAX_THIN: NonZeroI32 = NonZeroI32::new(if IsizeInPtr::MAX as u64 <= i32::MAX as u64 {
250 IsizeInPtr::MAX as i32
251 } else {
252 i32::MAX
253 })
254 .unwrap();
255
256 const fn non_zero(value: i32) -> NonZeroI32 {
257 NonZeroI32::new(value).unwrap()
258 }
259
260 #[test]
261 fn test_size_optimization() {
262 assert_eq!(
264 std::mem::size_of::<ThinStatus>(),
265 std::mem::size_of::<usize>()
266 );
267 }
268
269 #[test]
270 fn test_from_error_code_raw() {
271 let status: ThinStatus = status_code::ErrorCode::NotFound.into();
273 assert_eq!(i32::from(status.code_raw()), 5);
274 assert_eq!(status.message(), "");
275 #[cfg(feature = "use_any")]
276 assert_eq!(status.details(), &[]);
277 assert!(status.thin.has_number());
278 assert_eq!(format!("{}", status), "NOT_FOUND");
279 }
280
281 #[test]
282 fn test_from_code_within_max_thin() {
283 let status_pos = ThinStatus::from_code(MAX_THIN);
285 assert_eq!(status_pos.code_raw(), MAX_THIN);
286 assert_eq!(status_pos.message(), "");
287 assert!(status_pos.thin.has_number());
288
289 let status_neg = ThinStatus::from_code(-MAX_THIN);
290 assert_eq!(status_neg.code_raw(), -MAX_THIN);
291 assert_eq!(
292 status_neg.code_or_unknown(),
293 status_code::ErrorCode::Unknown
294 );
295 assert_eq!(status_neg.message(), "");
296 assert!(status_neg.thin.has_number());
297
298 let status_normal = ThinStatus::from_code(non_zero(42));
299 assert_eq!(status_normal.code_raw(), non_zero(42));
300 assert_eq!(status_normal.message(), "");
301 assert!(status_normal.thin.has_number());
302 }
303
304 #[test]
306 fn test_from_code_outside_max_thin() {
307 if MAX_THIN.get() < NonZeroI32::MAX.into() {
310 let larger: NonZeroI32 = non_zero(MAX_THIN.get() + 1);
311 let status_overflow = ThinStatus::from_code(larger);
312 assert_eq!(status_overflow.code_raw(), larger);
313 assert_eq!(status_overflow.message(), "");
314
315 let status_underflow = ThinStatus::from_code(-larger);
316 assert_eq!(status_underflow.code_raw(), -larger);
317 assert_eq!(status_underflow.message(), "");
318 assert!(status_overflow.thin.has_ref());
319 assert!(status_underflow.thin.has_ref());
320 }
321
322 let status_max = ThinStatus::from_code(NonZeroI32::MAX);
324 assert_eq!(status_max.code_raw(), NonZeroI32::MAX);
325 let status_min = ThinStatus::from_code(NonZeroI32::MIN);
326 assert_eq!(status_min.code_raw(), NonZeroI32::MIN);
327 if MAX_THIN.get() < NonZeroI32::MAX.into() {
328 assert!(status_max.thin.has_ref());
329 assert!(status_min.thin.has_ref());
330 }
331 }
332
333 #[test]
334 fn test_from_error_code_and_message() {
335 let mut builder = ThinStatus::builder(status_code::ErrorCode::NotFound);
336 write!(builder, "message").expect("write! failed");
337 let status = builder.build();
338 assert_eq!(i32::from(status.code_raw()), 5);
339 assert_eq!(status.code(), Some(status_code::ErrorCode::NotFound));
340 assert_eq!(status.code_or_unknown(), status_code::ErrorCode::NotFound);
341 assert_eq!(status.message(), "message");
342 #[cfg(feature = "use_any")]
343 assert_eq!(status.details(), &[]);
344 assert!(status.thin.has_ref());
345 assert_eq!(format!("{}", status), "NOT_FOUND: message");
346 }
347
348 #[test]
349 fn test_from_io_error() {
350 let err = std::io::Error::new(std::io::ErrorKind::NotFound, "meaning of life");
351 let status = ThinStatus::from(err);
352 assert_eq!(status.code(), Some(status_code::ErrorCode::NotFound));
353 assert_eq!(status.message(), "meaning of life");
354 }
355
356 #[cfg(feature = "use_any")]
357 #[test]
358 fn test_with_details() {
359 let detail =
360 google_cloud_wkt::Any::from_msg(&google_cloud_wkt::Duration::clamp(123, 456)).unwrap();
361 let status = ThinStatus::builder(status_code::ErrorCode::NotFound)
362 .add_detail(detail.clone())
363 .build();
364 assert_eq!(i32::from(status.code_raw()), 5);
365 assert_eq!(status.message(), "");
366 assert_eq!(status.details(), vec![detail]);
367 assert!(status.thin.has_ref());
368 let formatted = format!("{:#?}", status);
369 assert!(formatted.contains("Any"));
370 assert!(formatted.contains("type.googleapis.com/google.protobuf.Duration"));
371 assert!(formatted.contains("123"));
372 }
373
374 #[test]
375 fn test_clone_and_equality() {
376 let status1 = ThinStatus::builder(non_zero(13))
377 .message("Permission Denied")
378 .build();
379 let status2 = status1.clone();
380
381 assert_eq!(status1, status2);
382 assert_eq!(status1.code_raw(), status2.code_raw());
383 assert_eq!(status1.message(), status2.message());
384
385 let _status_different = ThinStatus::from_code(non_zero(13));
386 assert_ne!(status1, _status_different);
387 }
388
389 #[cfg(feature = "use_cloud_rpc")]
390 #[test]
391 fn test_cloud_rpc_status_conversions() {
392 assert!(
393 ThinStatus::try_from(cloud_rpc::Status::new()).is_err(),
394 "OK status shouldn't be convertible to ThinStatus"
395 );
396
397 let detail =
398 google_cloud_wkt::Any::from_msg(&google_cloud_wkt::Duration::clamp(123, 456)).unwrap();
399 let status = ThinStatus::builder(status_code::ErrorCode::NotFound)
400 .message("Be yourself! Everyone else is already taken.")
401 .add_detail(detail)
402 .build();
403 assert_eq!(
404 ThinStatus::try_from(cloud_rpc::Status::from(&status)).as_ref(),
405 Ok(&status)
406 );
407
408 assert_eq!(
409 ThinStatus::try_from(&cloud_rpc::Status::from(&status)),
410 Ok(status)
411 );
412 }
413}
414
415trait ThinStatusExtSealed {}
416
417#[allow(private_bounds)]
418pub trait ThinStatusExt: ThinStatusExtSealed {
419 fn error_code_builder(&self, code: status_code::ErrorCode) -> builder::ThinStatusBuilder<'_>;
420
421 fn error_code(&self, code: status_code::ErrorCode) -> ThinStatus {
422 self.error_code_builder(code).build()
423 }
424}
425
426impl<M: Display> ThinStatusExtSealed for M {}
427
428impl<M: Display> ThinStatusExt for M {
429 fn error_code_builder(&self, code: status_code::ErrorCode) -> builder::ThinStatusBuilder<'_> {
430 ThinStatus::builder(code).message(self.to_string())
431 }
432
433 fn error_code(&self, code: status_code::ErrorCode) -> ThinStatus {
434 self.error_code_builder(code).build()
435 }
436}
437
438#[cfg(test)]
439mod thin_status_ext_tests {
440 use super::*;
441
442 #[test]
443 fn test_from_error_code_and_message() {
444 let status = "message".error_code(status_code::ErrorCode::NotFound);
445 assert_eq!(i32::from(status.code_raw()), 5);
446 assert_eq!(status.code(), Some(status_code::ErrorCode::NotFound));
447 assert_eq!(status.message(), "message");
448 }
449}