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::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/// Holds a status code, and if enabled by feature `use_any`, also a list of `google_cloud_wkt.Any`
26/// instances that can provide structured details.
27#[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
34/// Coverts `FullStatus` to a code embedded inside a pointer as a tagged integer if:
35/// - There are no `google_cloud_wkt.Any` details attached.
36/// - The integer fits in `IsizeInPtr`.
37///
38/// Otherwise returns the very same instance.
39impl 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/// Holds a non-OK status code (in most cases `ErrorCode`), a descriptive string message, and
52/// optionally (if enabled by feature `use_any`) also `[google_cloud_wkt::Any]`.
53///
54/// It occupies only a single pointer word, which either wraps a tagged integer code (if there is
55/// neither message nor details), or a `ThinArc` pointer that holds a more complex value.
56/// Furthermore its representation is never `nullptr`, therefore `Option<ThinStatus>` and
57/// `Result<(), ThinStatus>` occupy just a single memory word.
58///
59/// Note that `Copy`, `Eq`, `PartialOrd` and `Hash` are only provided (derived) when feature
60/// `use_any` is disabled, since the `Any` datatype doesn't provide them.
61#[derive(Clone, Debug, PartialEq)]
62#[cfg_attr(not(feature = "use_any"), derive(Eq, PartialOrd, Ord, Hash))]
63pub struct ThinStatus {
64    // The wrapped `isize` integer is always non-zero.
65    // The `&[u8]` is always in UTF-8.
66    thin: ThinArcOrInt<FullStatus, u8>,
67}
68
69impl ThinStatus {
70    /// Constructs a builder that allows convenient creation of `ThinStatus` instances.
71    pub fn builder<'a, C: Into<NonZeroI32>>(code: C) -> builder::ThinStatusBuilder<'a> {
72        builder::ThinStatusBuilder::new(code)
73    }
74
75    /// Converts a builder into a new instance.
76    ///
77    /// This involves at most one heap allocation (if there is a message and/or `google_cloud_wkt.Any`.
78    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    /// A utility function to create a status with just a raw code. If it fits, it'll be stored just
85    /// as a tagged integer inside an internal pointer.
86    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    /// Returns the stored error code, or `None` if the raw numerical value doesn't match any of the
99    /// `ErrorCode` values.
100    pub fn code(&self) -> Option<status_code::ErrorCode> {
101        self.code_raw().get().try_into().ok()
102    }
103
104    /// Convenience wrapper to `code()` that converts an unknown error to `ErrorCode::Unknown`.
105    pub fn code_or_unknown(&self) -> status_code::ErrorCode {
106        self.code().unwrap_or(status_code::ErrorCode::Unknown)
107    }
108
109    /// Returns the raw numerical error code.
110    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    /// Returns the message attached to the status (if any).
122    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    /// Returns the list of `google_cloud_wkt::Any` objects attached to the status.
130    #[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    /// Converts a `ThinStatus` into a `google_cloud_rpc::Status`.
137    #[cfg(feature = "use_cloud_rpc")]
138    pub fn to_cloud_rpc(&self) -> cloud_rpc::Status {
139        self.into()
140    }
141
142    /// If a given `Status` is non-OK, converts it into `ThinStatus`.
143    #[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    /// If a given `Status` is non-OK, converts it into `ThinStatus`.
149    /// Details of type `google_cloud_wkt.Any` are cloned.
150    #[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/// If a given `Status` is non-OK, converts it into `ThinStatus`.
187#[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/// If a given `Status` is non-OK, converts it into `ThinStatus`.
201/// Details of type `google_cloud_wkt.Any` are cloned.
202#[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/// Converts a `ThinStatus` into a `google_cloud_rpc::Status`.
216#[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    /// Represents the maximal `i32` value that can be stored inside the integer variant of
233    /// `ThinArcOrInt`.
234    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        // Ensure that ThinStatus takes up exactly the size of a single pointer.
248        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        // Values exactly at the boundary and within MAX_THIN should not allocate a ThinArc.
257        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        // Values exactly at the boundary and within MAX_THIN should not allocate a ThinArc.
269        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    /// Values outside MAX_THIN must force a ThinArc allocation.
290    #[test]
291    fn test_from_code_outside_max_thin() {
292        // If MAX_THIN equals i32::MAX, testing values beyond the boundary
293        // is not meaningful due to i32 overflow, so we check if it is smaller first.
294        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        // Extreme i32 values (these will surely exceed MAX_THIN if MAX_THIN is smaller).
308        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}