hf_fetch_model/error.rs
1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3//! Error types for hf-fetch-model.
4//!
5//! All fallible operations in this crate return [`FetchError`].
6//! [`FileFailure`] provides structured per-file error reporting.
7
8use std::path::PathBuf;
9
10/// Errors that can occur during model fetching.
11#[derive(Debug, thiserror::Error)]
12#[non_exhaustive]
13pub enum FetchError {
14 /// The `hf-hub` API returned an error.
15 #[error("hf-hub API error: {0}")]
16 Api(#[from] hf_hub::api::tokio::ApiError),
17
18 /// An I/O error occurred while accessing the local filesystem.
19 #[error("I/O error at {path}: {source}")]
20 Io {
21 /// The path that caused the error.
22 path: PathBuf,
23 /// The underlying I/O error.
24 source: std::io::Error,
25 },
26
27 /// The repository was not found or is inaccessible.
28 #[error("repository not found: {repo_id}")]
29 RepoNotFound {
30 /// The repository identifier that was not found.
31 repo_id: String,
32 },
33
34 /// Authentication failed (missing or invalid token).
35 ///
36 /// Reserved for future use. Currently, auth failures surface as
37 /// [`FetchError::Api`] because `hf-hub` does not distinguish them.
38 #[error("authentication failed: {reason}")]
39 Auth {
40 /// Description of the authentication failure.
41 reason: String,
42 },
43
44 /// An invalid glob pattern was provided for filtering.
45 #[error("invalid glob pattern: {pattern}: {reason}")]
46 InvalidPattern {
47 /// The glob pattern that failed to parse.
48 pattern: String,
49 /// Description of the parse error.
50 reason: String,
51 },
52
53 /// SHA256 checksum mismatch after download.
54 #[error("checksum mismatch for {filename}: expected {expected}, got {actual}")]
55 Checksum {
56 /// The filename that failed verification.
57 filename: String,
58 /// The expected SHA256 hex digest.
59 expected: String,
60 /// The actual SHA256 hex digest computed from the file.
61 actual: String,
62 },
63
64 /// A download operation timed out.
65 #[error("timeout downloading {filename} after {seconds}s")]
66 Timeout {
67 /// The filename that timed out.
68 filename: String,
69 /// The timeout duration in seconds.
70 seconds: u64,
71 },
72
73 /// One or more files failed to download.
74 ///
75 /// Contains the successful path and a list of per-file failures.
76 #[error("{} file(s) failed to download:{}", failures.len(), format_failures(failures))]
77 PartialDownload {
78 /// The snapshot directory (if any files succeeded).
79 path: Option<PathBuf>,
80 /// Per-file failure details.
81 failures: Vec<FileFailure>,
82 },
83
84 /// A chunked (multi-connection) download failed.
85 #[error("chunked download failed for {filename}: {reason}")]
86 ChunkedDownload {
87 /// The filename that failed.
88 filename: String,
89 /// Description of the failure.
90 reason: String,
91 },
92
93 /// An HTTP request to the `HuggingFace` API failed.
94 #[error("HTTP error: {0}")]
95 Http(String),
96
97 /// An invalid argument was provided.
98 #[error("{0}")]
99 InvalidArgument(String),
100
101 /// The repository exists but no files matched after filtering,
102 /// or the repository contains no files at all.
103 #[error("no files matched in repository {repo_id}")]
104 NoFilesMatched {
105 /// The repository identifier.
106 repo_id: String,
107 },
108
109 /// A `.safetensors` header is malformed or cannot be parsed.
110 #[error("safetensors header error for {filename}: {reason}")]
111 SafetensorsHeader {
112 /// The filename whose header failed to parse.
113 filename: String,
114 /// Description of the parse failure.
115 reason: String,
116 },
117
118 /// `inspect` was asked to read a file whose extension is not supported.
119 ///
120 /// Emitted before any parse attempt so users see a clear format mismatch
121 /// rather than a misleading header-parse error.
122 #[error("hf-fm inspect supports .safetensors only (got .{extension} for {filename})")]
123 UnsupportedInspectFormat {
124 /// The filename whose extension is unsupported.
125 filename: String,
126 /// The actual extension without the leading dot, or `unknown` if none.
127 extension: String,
128 },
129}
130
131/// A per-file download failure with structured context.
132#[derive(Debug, Clone)]
133pub struct FileFailure {
134 /// The filename that failed.
135 pub filename: String,
136 /// Human-readable description of the failure.
137 pub reason: String,
138 /// Whether this failure is likely to succeed on retry.
139 pub retryable: bool,
140}
141
142impl std::fmt::Display for FileFailure {
143 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
144 write!(
145 f,
146 "{}: {} (retryable: {})",
147 self.filename, self.reason, self.retryable
148 )
149 }
150}
151
152/// Formats a list of file failures for inclusion in the `PartialDownload` error message.
153fn format_failures(failures: &[FileFailure]) -> String {
154 let mut s = String::new();
155 for f in failures {
156 s.push_str("\n - ");
157 s.push_str(f.filename.as_str());
158 s.push_str(": ");
159 s.push_str(f.reason.as_str());
160 }
161 s
162}