google_cloud_storage/
error.rs

1// Copyright 2025 Google LLC
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//     https://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//! Custom errors for the Cloud Storage client.
16//!
17//! The storage client defines additional error types. These are often returned
18//! as the `source()` of an [Error][crate::Error].
19
20use crate::model::ObjectChecksums;
21
22#[derive(Clone, Debug)]
23#[non_exhaustive]
24pub enum ChecksumMismatch {
25    /// The CRC32C checksum sent by the service does not match the computed (or expected) value.
26    Crc32c { got: u32, want: u32 },
27
28    /// The MD5 hash sent by the service does not match the computed (or expected) value.
29    Md5 {
30        got: bytes::Bytes,
31        want: bytes::Bytes,
32    },
33
34    /// The CRC32C checksum **and** the MD5 hash sent by the service do not
35    /// match the computed (or expected) values.
36    Both {
37        got: Box<ObjectChecksums>,
38        want: Box<ObjectChecksums>,
39    },
40}
41
42impl std::fmt::Display for ChecksumMismatch {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        match self {
45            Self::Crc32c { got, want } => write!(
46                f,
47                "the CRC32C checksums do not match: got=0x{got:08x}, want=0x{want:08x}"
48            ),
49            Self::Md5 { got, want } => write!(
50                f,
51                "the MD5 hashes do not match: got={:0x?}, want={:0x?}",
52                &got, &want
53            ),
54            Self::Both { got, want } => {
55                write!(
56                    f,
57                    "both the CRC32C checksums and MD5 hashes do not match: got.crc32c=0x{:08x}, want.crc32c=0x{:08x}, got.md5={:x?}, want.md5={:x?}",
58                    got.crc32c.unwrap_or_default(),
59                    want.crc32c.unwrap_or_default(),
60                    got.md5_hash,
61                    want.md5_hash
62                )
63            }
64        }
65    }
66}
67
68/// Represents errors that can occur when converting to [KeyAes256] instances.
69///
70/// # Example:
71/// ```
72/// # use google_cloud_storage::{builder::storage::KeyAes256, error::KeyAes256Error};
73/// let invalid_key_bytes: &[u8] = b"too_short_key"; // Less than 32 bytes
74/// let result = KeyAes256::new(invalid_key_bytes);
75///
76/// assert!(matches!(result, Err(KeyAes256Error::InvalidLength)));
77/// ```
78///
79/// [KeyAes256]: crate::builder::storage::KeyAes256
80#[derive(thiserror::Error, Debug)]
81#[non_exhaustive]
82pub enum KeyAes256Error {
83    /// The provided key's length was not exactly 32 bytes.
84    #[error("Key has an invalid length: expected 32 bytes.")]
85    InvalidLength,
86}
87
88/// Represents an error that can occur when invalid range is specified.
89#[derive(thiserror::Error, Debug)]
90#[non_exhaustive]
91pub enum RangeError {
92    /// The provided read limit was negative.
93    #[error("read limit was negative, expected non-negative value.")]
94    NegativeLimit,
95    /// A negative offset was provided with a read limit.
96    #[error("negative read offsets cannot be used with read limits.")]
97    NegativeOffsetWithLimit,
98}
99
100/// Represents an error that can occur when reading response data.
101#[derive(thiserror::Error, Debug)]
102#[non_exhaustive]
103pub enum ReadError {
104    /// The calculated crc32c did not match server provided crc32c.
105    #[error("checksum mismatch {0}")]
106    ChecksumMismatch(ChecksumMismatch),
107
108    /// The read was interrupted before all the expected bytes arrived.
109    #[error("missing {0} bytes at the end of the stream")]
110    ShortRead(u64),
111
112    /// The read received more bytes than expected.
113    #[error("too many bytes received: expected {expected}, stopped download at {got}")]
114    LongRead { got: u64, expected: u64 },
115
116    /// Only 200 and 206 status codes are expected in successful responses.
117    #[error("unexpected success code {0} in read request, only 200 and 206 are expected")]
118    UnexpectedSuccessCode(u16),
119
120    /// Successful HTTP response must include some headers.
121    #[error("the response is missing '{0}', a required header")]
122    MissingHeader(&'static str),
123
124    /// The received header format is invalid.
125    #[error("the format for header '{0}' is incorrect")]
126    BadHeaderFormat(
127        &'static str,
128        #[source] Box<dyn std::error::Error + Send + Sync + 'static>,
129    ),
130}
131
132/// An unrecoverable problem in the upload protocol.
133///
134/// # Example
135/// ```
136/// # use google_cloud_storage::{client::Storage, error::UploadError};
137/// # async fn sample(client: &Storage) -> anyhow::Result<()> {
138/// use std::error::Error as _;
139/// let upload = client
140///     .upload_object("projects/_/buckets/my-bucket", "my-object", "hello world")
141///     .with_if_generation_not_match(0);
142/// match upload.send_buffered().await {
143///     Ok(object) => println!("Successfully uploaded the object"),
144///     Err(error) if error.is_serialization() => {
145///         println!("Some problem {error:?} sending the data to the service");
146///         if let Some(m) = error.source().and_then(|e| e.downcast_ref::<UploadError>()) {
147///             println!("{m}");
148///         }
149///     },
150///     Err(e) => return Err(e.into()), // not handled in this example
151/// }
152/// # Ok(()) }
153/// ```
154///
155#[derive(thiserror::Error, Debug)]
156#[non_exhaustive]
157pub enum UploadError {
158    /// The service has "uncommitted" previously persisted bytes.
159    ///
160    /// # Troubleshoot
161    ///
162    /// In the resumable upload protocol the service reports how many bytes are
163    /// persisted. This error indicates that the service previously reported
164    /// more bytes as persisted than in the latest report. This could indicate:
165    /// - a corrupted message from the service, either the earlier message
166    ///   reporting more bytes persisted than actually are, or the current
167    ///   message indicating fewer bytes persisted.
168    /// - a bug in the service, where it reported bytes as persisted when they
169    ///   were not.
170    /// - a bug in the client, maybe storing the incorrect byte count, or
171    ///   parsing the messages incorrectly.
172    ///
173    /// All of these conditions indicate a bug, and in Rust it is idiomatic to
174    /// `panic!()` when a bug is detected. However, in this case it seems more
175    /// appropriate to report the problem, as the client library cannot
176    /// determine the location of the bug.
177    #[error(
178        "the service previously persisted {offset} bytes, but now reports only {persisted} as persisted"
179    )]
180    UnexpectedRewind { offset: u64, persisted: u64 },
181
182    /// The service reports more bytes persisted than sent.
183    ///
184    /// # Troubleshoot
185    ///
186    /// Most likely this indicates that two concurrent uploads are using the
187    /// same session. Review your application design to avoid concurrent
188    /// uploads.
189    ///
190    /// It is possible that this indicates a bug in the service, client, or
191    /// messages corrupted in transit.
192    #[error("the service reports {persisted} bytes as persisted, but we only sent {sent} bytes")]
193    TooMuchProgress { sent: u64, persisted: u64 },
194
195    /// The checksums reported by the service do not match the expected checksums.
196    ///
197    /// # Troubleshoot
198    ///
199    /// The client library compares the CRC32C checksum and/or MD5 hash of the
200    /// uploaded data against the hash reported by the service at the end of
201    /// the upload. This error indicates the hashes did not match.
202    ///
203    /// If you provided known values for these checksums verify those values are
204    /// correct.
205    ///
206    /// Otherwise, this is probably a data corruption problem. These are
207    /// notoriously difficult to root cause. They probably indicate faulty
208    /// equipment, such as the physical machine hosting your client, the network
209    /// elements between your client and the service, or the physical machine
210    /// hosting the service.
211    ///
212    /// If possible, resend the data from a different machine.
213    #[error("checksum mismatch {0}")]
214    ChecksumMismatch(ChecksumMismatch),
215}
216
217#[cfg(test)]
218mod tests {
219    use super::*;
220
221    #[test]
222    fn mismatch_crc32c() {
223        let value = ChecksumMismatch::Crc32c {
224            got: 0x01020304_u32,
225            want: 0x02030405_u32,
226        };
227        let fmt = value.to_string();
228        assert!(fmt.contains("got=0x01020304"), "{value:?} => {fmt}");
229        assert!(fmt.contains("want=0x02030405"), "{value:?} => {fmt}");
230    }
231
232    #[test]
233    fn mismatch_md5() {
234        let value = ChecksumMismatch::Md5 {
235            got: bytes::Bytes::from_owner([0x01_u8, 0x02, 0x03, 0x04]),
236            want: bytes::Bytes::from_owner([0x02_u8, 0x03, 0x04, 0x05]),
237        };
238        let fmt = value.to_string();
239        assert!(
240            fmt.contains(r#"got=b"\x01\x02\x03\x04""#),
241            "{value:?} => {fmt}"
242        );
243        assert!(
244            fmt.contains(r#"want=b"\x02\x03\x04\x05""#),
245            "{value:?} => {fmt}"
246        );
247    }
248
249    #[test]
250    fn mismatch_both() {
251        let got = ObjectChecksums::new()
252            .set_crc32c(0x01020304_u32)
253            .set_md5_hash(bytes::Bytes::from_owner([0x01_u8, 0x02, 0x03, 0x04]));
254        let want = ObjectChecksums::new()
255            .set_crc32c(0x02030405_u32)
256            .set_md5_hash(bytes::Bytes::from_owner([0x02_u8, 0x03, 0x04, 0x05]));
257        let value = ChecksumMismatch::Both {
258            got: Box::new(got),
259            want: Box::new(want),
260        };
261        let fmt = value.to_string();
262        assert!(fmt.contains("got.crc32c=0x01020304"), "{value:?} => {fmt}");
263        assert!(fmt.contains("want.crc32c=0x02030405"), "{value:?} => {fmt}");
264        assert!(
265            fmt.contains(r#"got.md5=b"\x01\x02\x03\x04""#),
266            "{value:?} => {fmt}"
267        );
268        assert!(
269            fmt.contains(r#"want.md5=b"\x02\x03\x04\x05""#),
270            "{value:?} => {fmt}"
271        );
272    }
273}