Skip to main content

thin_status/
thin_status.rs

1// Copyright 2026 <https://github.com/ppetr/>
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15#[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/// Holds a status code, and if enabled by feature `use_any`, also a list of `google_cloud_wkt.Any`
29/// instances that can provide structured details.
30#[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
37/// Coverts `FullStatus` to a code embedded inside a pointer as a tagged integer if:
38/// - There are no `google_cloud_wkt.Any` details attached.
39/// - The integer fits in `IsizeInPtr`.
40///
41/// Otherwise returns the very same instance.
42impl 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/// Holds a non-OK status code (in most cases `ErrorCode`), a descriptive string message, and
55/// optionally (if enabled by feature `use_any`) also `[google_cloud_wkt::Any]`.
56///
57/// It occupies only a single pointer word, which either wraps a tagged integer code (if there is
58/// neither message nor details), or a `ThinArc` pointer that holds a more complex value.
59/// Furthermore its representation is never `nullptr`, therefore `Option<ThinStatus>` and
60/// `Result<(), ThinStatus>` occupy just a single memory word.
61///
62/// Note that `Copy`, `Eq`, `PartialOrd` and `Hash` are only provided (derived) when feature
63/// `use_any` is disabled, since the `Any` datatype doesn't provide them.
64#[derive(Clone, Debug, PartialEq)]
65#[cfg_attr(not(feature = "use_any"), derive(Eq, PartialOrd, Ord, Hash))]
66#[must_use = "this `ThinStatus` represents an error that should be handled"]
67pub struct ThinStatus {
68    // The wrapped `isize` integer is always non-zero.
69    // The `&[u8]` is always in UTF-8.
70    thin: ThinArcOrInt<FullStatus, u8>,
71}
72
73impl ThinStatus {
74    /// Constructs a builder that allows convenient creation of `ThinStatus` instances.
75    pub fn builder<'a, C: Into<NonZeroI32>>(code: C) -> builder::ThinStatusBuilder<'a> {
76        builder::ThinStatusBuilder::new(code)
77    }
78
79    /// Converts a builder into a new instance.
80    ///
81    /// This involves at most one heap allocation (if there is a message and/or `google_cloud_wkt.Any`.
82    pub(crate) fn from_builder(builder: builder::ThinStatusBuilder) -> Self {
83        ThinStatus {
84            thin: ThinArcOrInt::from_convertible(builder.full, builder.message.as_bytes()),
85        }
86    }
87
88    /// A utility function to create a status with just a raw code. If it fits, it'll be stored just
89    /// as a tagged integer inside an internal pointer.
90    pub fn from_code<C: Into<NonZeroI32>>(code: C) -> Self {
91        ThinStatus {
92            thin: ThinArcOrInt::from_convertible(
93                FullStatus {
94                    code: code.into(),
95                    details: Default::default(),
96                },
97                &[],
98            ),
99        }
100    }
101
102    /// Returns the stored error code, or `None` if the raw numerical value doesn't match any of the
103    /// `ErrorCode` values.
104    pub fn code(&self) -> Option<status_code::ErrorCode> {
105        self.code_raw().get().try_into().ok()
106    }
107
108    /// Convenience wrapper to `code()` that converts an unknown error to `ErrorCode::Unknown`.
109    pub fn code_or_unknown(&self) -> status_code::ErrorCode {
110        self.code().unwrap_or(status_code::ErrorCode::Unknown)
111    }
112
113    /// Returns the raw numerical error code.
114    pub fn code_raw(&self) -> NonZeroI32 {
115        match self.thin.as_isize() {
116            Some(code) => unsafe { NonZeroI32::new_unchecked(code as i32) },
117            None => {
118                let val = &self.thin.as_arc();
119                let val = val.expect("ThinArcOrInt contains neither code nor arc");
120                val.header.header.code
121            }
122        }
123    }
124
125    /// Returns the message attached to the status (if any).
126    pub fn message(&self) -> &str {
127        match self.thin.as_arc() {
128            None => "",
129            Some(arc) => unsafe { str::from_utf8_unchecked(&arc.slice) },
130        }
131    }
132
133    /// Returns the list of `google_cloud_wkt::Any` objects attached to the status.
134    #[cfg(feature = "use_any")]
135    pub fn details(&self) -> &[google_cloud_wkt::Any] {
136        let &arc = &self.thin.as_arc();
137        arc.map_or::<&[google_cloud_wkt::Any], _>(&[], |t| &t.header.header.details.get())
138    }
139
140    /// Converts a `ThinStatus` into a `google_cloud_rpc::Status`.
141    #[cfg(feature = "use_cloud_rpc")]
142    pub fn to_cloud_rpc(&self) -> cloud_rpc::Status {
143        self.into()
144    }
145
146    /// If a given `Status` is non-OK, converts it into `ThinStatus`.
147    #[cfg(feature = "use_cloud_rpc")]
148    pub fn try_from_cloud_rpc(status: cloud_rpc::Status) -> Result<Self, ()> {
149        status.try_into()
150    }
151
152    /// If a given `Status` is non-OK, converts it into `ThinStatus`.
153    /// Details of type `google_cloud_wkt.Any` are cloned.
154    #[cfg(feature = "use_cloud_rpc")]
155    pub fn try_from_cloud_rpc_ref(status: &cloud_rpc::Status) -> Result<Self, ()> {
156        status.try_into()
157    }
158}
159
160/// If the contained error code corresponds to an `ErrorCode` (`code()` returns `Some(...)`), it's
161/// printed as an upper-case string (such as `NOT_FOUND`); otherwise just as a number.
162/// This is followed by a colon, space and the contained message (if there is any).
163impl Display for ThinStatus {
164    /// If the `alternate` flag is set, always prints the error code as a number.
165    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
166        let code = self.code_raw().get();
167        if let Some(error_code) = status_code::ErrorCode::try_from(code).ok() {
168            error_code.fmt(f)?;
169        } else {
170            code.fmt(f)?;
171        }
172        let msg = self.message();
173        if !msg.is_empty() {
174            write!(f, ": {}", msg)?;
175        }
176        Ok(())
177    }
178}
179
180impl Error for ThinStatus {}
181
182impl From<status_code::ErrorCode> for ThinStatus {
183    fn from(code: status_code::ErrorCode) -> Self {
184        Self::from_code(NonZeroI32::from(code))
185    }
186}
187
188impl From<std::io::Error> for ThinStatus {
189    fn from(err: std::io::Error) -> Self {
190        Self::from(&err)
191    }
192}
193
194impl From<&std::io::Error> for ThinStatus {
195    fn from(err: &std::io::Error) -> Self {
196        let mut builder = Self::builder(status_code::ErrorCode::from_error_kind(err.kind()));
197        let _ = write!(builder, "{}", err);
198        builder.build()
199    }
200}
201
202/// If a given `Status` is non-OK, converts it into `ThinStatus`.
203#[cfg(feature = "use_cloud_rpc")]
204impl TryFrom<cloud_rpc::Status> for ThinStatus {
205    type Error = ();
206
207    fn try_from(status: cloud_rpc::Status) -> Result<Self, ()> {
208        let code: NonZeroI32 = status.code.try_into().map_err(|_| ())?;
209        Ok(Self::builder(code)
210            .message(&status.message)
211            .details(status.details)
212            .build())
213    }
214}
215
216/// If a given `Status` is non-OK, converts it into `ThinStatus`.
217/// Details of type `google_cloud_wkt.Any` are cloned.
218#[cfg(feature = "use_cloud_rpc")]
219impl TryFrom<&cloud_rpc::Status> for ThinStatus {
220    type Error = ();
221
222    fn try_from(status: &cloud_rpc::Status) -> Result<Self, ()> {
223        let code: NonZeroI32 = status.code.try_into().map_err(|_| ())?;
224        Ok(Self::builder(code)
225            .message(&status.message)
226            .details(status.details.clone())
227            .build())
228    }
229}
230
231/// Converts a `ThinStatus` into a `google_cloud_rpc::Status`.
232#[cfg(feature = "use_cloud_rpc")]
233impl From<&ThinStatus> for cloud_rpc::Status {
234    fn from(status: &ThinStatus) -> Self {
235        let mut result = Self::new();
236        result.code = status.code_raw().get();
237        result.message = status.message().to_string();
238        result.details = status.details().to_vec();
239        result
240    }
241}
242
243#[cfg(test)]
244mod thin_status_tests {
245    use super::*;
246    use std::fmt::Write;
247
248    /// Represents the maximal `i32` value that can be stored inside the integer variant of
249    /// `ThinArcOrInt`.
250    const MAX_THIN: NonZeroI32 = NonZeroI32::new(if IsizeInPtr::MAX as u64 <= i32::MAX as u64 {
251        IsizeInPtr::MAX as i32
252    } else {
253        i32::MAX
254    })
255    .unwrap();
256
257    const fn non_zero(value: i32) -> NonZeroI32 {
258        NonZeroI32::new(value).unwrap()
259    }
260
261    #[test]
262    fn test_size_optimization() {
263        // Ensure that ThinStatus takes up exactly the size of a single pointer.
264        assert_eq!(
265            std::mem::size_of::<ThinStatus>(),
266            std::mem::size_of::<usize>()
267        );
268    }
269
270    #[test]
271    fn test_from_error_code_raw() {
272        // Values exactly at the boundary and within MAX_THIN should not allocate a ThinArc.
273        let status: ThinStatus = status_code::ErrorCode::NotFound.into();
274        assert_eq!(i32::from(status.code_raw()), 5);
275        assert_eq!(status.message(), "");
276        #[cfg(feature = "use_any")]
277        assert_eq!(status.details(), &[]);
278        assert!(status.thin.has_number());
279        assert_eq!(format!("{}", status), "NOT_FOUND");
280    }
281
282    #[test]
283    fn test_from_code_within_max_thin() {
284        // Values exactly at the boundary and within MAX_THIN should not allocate a ThinArc.
285        let status_pos = ThinStatus::from_code(MAX_THIN);
286        assert_eq!(status_pos.code_raw(), MAX_THIN);
287        assert_eq!(status_pos.message(), "");
288        assert!(status_pos.thin.has_number());
289
290        let status_neg = ThinStatus::from_code(-MAX_THIN);
291        assert_eq!(status_neg.code_raw(), -MAX_THIN);
292        assert_eq!(
293            status_neg.code_or_unknown(),
294            status_code::ErrorCode::Unknown
295        );
296        assert_eq!(status_neg.message(), "");
297        assert!(status_neg.thin.has_number());
298
299        let status_normal = ThinStatus::from_code(non_zero(42));
300        assert_eq!(status_normal.code_raw(), non_zero(42));
301        assert_eq!(status_normal.message(), "");
302        assert!(status_normal.thin.has_number());
303    }
304
305    /// Values outside MAX_THIN must force a ThinArc allocation.
306    #[test]
307    fn test_from_code_outside_max_thin() {
308        // If MAX_THIN equals i32::MAX, testing values beyond the boundary
309        // is not meaningful due to i32 overflow, so we check if it is smaller first.
310        if MAX_THIN.get() < NonZeroI32::MAX.into() {
311            let larger: NonZeroI32 = non_zero(MAX_THIN.get() + 1);
312            let status_overflow = ThinStatus::from_code(larger);
313            assert_eq!(status_overflow.code_raw(), larger);
314            assert_eq!(status_overflow.message(), "");
315
316            let status_underflow = ThinStatus::from_code(-larger);
317            assert_eq!(status_underflow.code_raw(), -larger);
318            assert_eq!(status_underflow.message(), "");
319            assert!(status_overflow.thin.has_ref());
320            assert!(status_underflow.thin.has_ref());
321        }
322
323        // Extreme i32 values (these will surely exceed MAX_THIN if MAX_THIN is smaller).
324        let status_max = ThinStatus::from_code(NonZeroI32::MAX);
325        assert_eq!(status_max.code_raw(), NonZeroI32::MAX);
326        let status_min = ThinStatus::from_code(NonZeroI32::MIN);
327        assert_eq!(status_min.code_raw(), NonZeroI32::MIN);
328        if MAX_THIN.get() < NonZeroI32::MAX.into() {
329            assert!(status_max.thin.has_ref());
330            assert!(status_min.thin.has_ref());
331        }
332    }
333
334    #[test]
335    fn test_from_error_code_and_message() {
336        let mut builder = ThinStatus::builder(status_code::ErrorCode::NotFound);
337        write!(builder, "message").expect("write! failed");
338        let status = builder.build();
339        assert_eq!(i32::from(status.code_raw()), 5);
340        assert_eq!(status.code(), Some(status_code::ErrorCode::NotFound));
341        assert_eq!(status.code_or_unknown(), status_code::ErrorCode::NotFound);
342        assert_eq!(status.message(), "message");
343        #[cfg(feature = "use_any")]
344        assert_eq!(status.details(), &[]);
345        assert!(status.thin.has_ref());
346        assert_eq!(format!("{}", status), "NOT_FOUND: message");
347    }
348
349    #[test]
350    fn test_from_io_error() {
351        let err = std::io::Error::new(std::io::ErrorKind::NotFound, "meaning of life");
352        let status = ThinStatus::from(err);
353        assert_eq!(status.code(), Some(status_code::ErrorCode::NotFound));
354        assert_eq!(status.message(), "meaning of life");
355    }
356
357    #[cfg(feature = "use_any")]
358    #[test]
359    fn test_with_details() {
360        let detail =
361            google_cloud_wkt::Any::from_msg(&google_cloud_wkt::Duration::clamp(123, 456)).unwrap();
362        let status = ThinStatus::builder(status_code::ErrorCode::NotFound)
363            .add_detail(detail.clone())
364            .build();
365        assert_eq!(i32::from(status.code_raw()), 5);
366        assert_eq!(status.message(), "");
367        assert_eq!(status.details(), vec![detail]);
368        assert!(status.thin.has_ref());
369        let formatted = format!("{:#?}", status);
370        assert!(formatted.contains("Any"));
371        assert!(formatted.contains("type.googleapis.com/google.protobuf.Duration"));
372        assert!(formatted.contains("123"));
373    }
374
375    #[test]
376    fn test_clone_and_equality() {
377        let status1 = ThinStatus::builder(non_zero(13))
378            .message("Permission Denied")
379            .build();
380        let status2 = status1.clone();
381
382        assert_eq!(status1, status2);
383        assert_eq!(status1.code_raw(), status2.code_raw());
384        assert_eq!(status1.message(), status2.message());
385
386        let _status_different = ThinStatus::from_code(non_zero(13));
387        assert_ne!(status1, _status_different);
388    }
389
390    #[cfg(feature = "use_cloud_rpc")]
391    #[test]
392    fn test_cloud_rpc_status_conversions() {
393        assert!(
394            ThinStatus::try_from(cloud_rpc::Status::new()).is_err(),
395            "OK status shouldn't be convertible to ThinStatus"
396        );
397
398        let detail =
399            google_cloud_wkt::Any::from_msg(&google_cloud_wkt::Duration::clamp(123, 456)).unwrap();
400        let status = ThinStatus::builder(status_code::ErrorCode::NotFound)
401            .message("Be yourself! Everyone else is already taken.")
402            .add_detail(detail)
403            .build();
404        assert_eq!(
405            ThinStatus::try_from(cloud_rpc::Status::from(&status)).as_ref(),
406            Ok(&status)
407        );
408
409        assert_eq!(
410            ThinStatus::try_from(&cloud_rpc::Status::from(&status)),
411            Ok(status)
412        );
413    }
414}
415
416trait ThinStatusExtSealed {}
417
418#[allow(private_bounds)]
419pub trait ThinStatusExt: ThinStatusExtSealed {
420    fn error_code_builder(&self, code: status_code::ErrorCode) -> builder::ThinStatusBuilder<'_>;
421
422    fn error_code(&self, code: status_code::ErrorCode) -> ThinStatus {
423        self.error_code_builder(code).build()
424    }
425}
426
427impl<M: Display> ThinStatusExtSealed for M {}
428
429impl<M: Display> ThinStatusExt for M {
430    fn error_code_builder(&self, code: status_code::ErrorCode) -> builder::ThinStatusBuilder<'_> {
431        ThinStatus::builder(code).message(self.to_string())
432    }
433
434    fn error_code(&self, code: status_code::ErrorCode) -> ThinStatus {
435        self.error_code_builder(code).build()
436    }
437}
438
439#[cfg(test)]
440mod thin_status_ext_tests {
441    use super::*;
442
443    #[test]
444    fn test_from_error_code_and_message() {
445        let status = "message".error_code(status_code::ErrorCode::NotFound);
446        assert_eq!(i32::from(status.code_raw()), 5);
447        assert_eq!(status.code(), Some(status_code::ErrorCode::NotFound));
448        assert_eq!(status.message(), "message");
449    }
450}