1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
//! Failures while resolving or preprocessing an image.
use deepseek_recipe_core::multimodal::CalcResizeError;
/// Failure to fetch or preprocess one image.
#[derive(Debug, thiserror::Error)]
pub enum ImageError {
/// The image source needs a capability this implementation does not provide.
#[error("unsupported image source: {0}")]
Unsupported(&'static str),
/// The external image URL is too long.
#[error("external link length {len} too long, max link length {max}")]
UrlTooLong {
/// Length of the rejected URL.
len: usize,
/// Maximum accepted length.
max: usize,
},
/// The external image reference lacks the required `http` prefix.
#[error("invalid url: {url:?}")]
InvalidUrl {
/// Rejected URL.
url: String,
},
/// The request carries more images than the limit allows.
#[error("too many images: max {max} images per request, got {count}")]
TooManyImages {
/// Maximum number of images per request.
max: usize,
/// Number of images in the request.
count: usize,
},
/// One image is larger than the limit allows.
#[error("image size {size} bytes exceeds the limit of {max} bytes")]
ImageTooLarge {
/// Size of the rejected image.
size: usize,
/// Maximum accepted size.
max: usize,
},
/// The images together are larger than the limit allows.
#[error("total image size {size} bytes exceeds the limit of {max} bytes")]
TotalSizeTooLarge {
/// Total size of all images.
size: usize,
/// Maximum accepted total size.
max: usize,
},
/// The data URL or its encoded body is malformed.
#[error("invalid data url: {0}")]
InvalidDataUrl(String),
/// The image bytes are empty.
#[error("input image data is empty")]
EmptyImage,
/// The image media type is unsupported.
#[error("unsupported image media type: {0}")]
UnsupportedMediaType(String),
/// The image dimensions exceed the limit.
#[error("image dimensions exceed the maximum allowed")]
ImageDimensionsTooLarge,
/// The image could not be decoded.
#[error("failed to decode image: {0}")]
Decode(String),
/// The image could not be resized.
#[error("failed to resize image: {0}")]
Resize(String),
/// The image could not be encoded.
#[error("failed to encode image: {0}")]
Encode(String),
/// The image encoder reported failure without an error message.
#[error("failed to encode image")]
EncodeFailed,
/// Fitting the image into the token budget failed.
#[error(transparent)]
TokenBudget(#[from] CalcResizeError),
/// The image could not be fetched.
#[error("failed to fetch image from {url}: {source}")]
Fetch {
/// Rejected URL.
url: String,
/// Underlying fetcher failure, including wrapped input or size errors.
#[source]
source: Box<dyn std::error::Error + Send + Sync>,
},
/// The image server returned a non-success status.
#[error("failed to fetch image from {url}: unexpected status {status}")]
FetchStatus {
/// Rejected URL.
url: String,
/// HTTP status code of the response.
status: u16,
},
/// The HTTP client could not be constructed.
#[error("failed to create the HTTP client: {0}")]
Client(String),
}
impl ImageError {
/// Return whether another attempt of the same operation may succeed.
///
/// `Fetch` is retryable regardless of its source; `FetchStatus` is retryable
/// for status codes at least 500. Other variants are not retryable. The
/// resolver evaluates the fetcher's error before wrapping it in `Fetch`,
/// so a returned wrapper may have a different classification.
pub fn is_retryable(&self) -> bool {
match self {
Self::Fetch { .. } => true,
Self::FetchStatus { status, .. } => *status >= 500,
_ => false,
}
}
}