webdataset_core/error.rs
1//! Error type shared across the `webdataset` crates.
2
3use crate::prelude::*;
4use core::fmt;
5
6/// Convenience alias for results produced by this workspace.
7pub type Result<T> = core::result::Result<T, Error>;
8
9/// The error type used throughout the webdataset crates.
10///
11/// Errors carry enough context to be reported usefully by the pluggable
12/// [`Handler`](crate::handlers::Handler) implementations: the URL of the shard
13/// and the key of the sample are attached as they travel up the pipeline.
14#[derive(Debug, thiserror::Error)]
15#[non_exhaustive]
16pub enum Error {
17 /// An underlying I/O failure.
18 #[cfg(feature = "std")]
19 #[error("i/o error: {0}")]
20 Io(#[from] std::io::Error),
21
22 /// A JSON value could not be parsed or serialized.
23 #[cfg(feature = "json")]
24 #[error("json error: {0}")]
25 Json(#[from] serde_json::Error),
26
27 /// Bytes that were expected to be UTF-8 were not.
28 #[error("utf-8 error: {0}")]
29 Utf8(#[from] core::str::Utf8Error),
30
31 /// A sample field could not be decoded.
32 #[error("cannot decode {key}: {message}")]
33 Decode {
34 /// The field (file extension) that failed to decode.
35 key: String,
36 /// A human readable description of the failure.
37 message: String,
38 },
39
40 /// A sample field could not be encoded for writing.
41 #[error("cannot encode {key}: {message}")]
42 Encode {
43 /// The field that failed to encode.
44 key: String,
45 /// A human readable description of the failure.
46 message: String,
47 },
48
49 /// A required key was not present in a sample.
50 #[error("missing key: did not find {wanted:?} in {available:?}")]
51 MissingKey {
52 /// The key(s) that were looked up.
53 wanted: Vec<String>,
54 /// The keys the sample actually had.
55 available: Vec<String>,
56 },
57
58 /// The same key occurred twice while grouping files into a sample.
59 #[error("duplicate key {key} in sample {sample_key}")]
60 DuplicateKey {
61 /// The duplicated field name.
62 key: String,
63 /// The key of the sample being assembled.
64 sample_key: String,
65 },
66
67 /// An operation was refused because secure mode is enabled.
68 #[error("refused in secure mode: {0}")]
69 Security(String),
70
71 /// A subprocess used to open a URL failed.
72 #[error("subprocess failed: {0}")]
73 Subprocess(String),
74
75 /// Malformed input data (bad archive, bad header, bad brace expression).
76 #[error("malformed input: {0}")]
77 Format(String),
78
79 /// The requested functionality is not available in this build.
80 #[error("unsupported: {0}")]
81 Unsupported(String),
82
83 /// A value had the right shape but the wrong contents.
84 #[error("invalid value: {0}")]
85 Value(String),
86
87 /// A pipeline stage that was declared non-empty produced no data.
88 #[error("empty: {0}")]
89 Empty(String),
90
91 /// An error with additional context attached.
92 #[error("{context}: {source}")]
93 Context {
94 /// What was being attempted.
95 context: String,
96 /// The underlying error.
97 #[source]
98 source: Box<Error>,
99 },
100}
101
102impl Error {
103 /// Build an [`Error::Format`] from anything printable.
104 pub fn format(message: impl fmt::Display) -> Self {
105 Error::Format(message.to_string())
106 }
107
108 /// Build an [`Error::Value`] from anything printable.
109 pub fn value(message: impl fmt::Display) -> Self {
110 Error::Value(message.to_string())
111 }
112
113 /// Build an [`Error::Unsupported`] from anything printable.
114 pub fn unsupported(message: impl fmt::Display) -> Self {
115 Error::Unsupported(message.to_string())
116 }
117
118 /// Build an [`Error::Decode`] for the given field.
119 pub fn decode(key: impl Into<String>, message: impl fmt::Display) -> Self {
120 Error::Decode { key: key.into(), message: message.to_string() }
121 }
122
123 /// Build an [`Error::Encode`] for the given field.
124 pub fn encode(key: impl Into<String>, message: impl fmt::Display) -> Self {
125 Error::Encode { key: key.into(), message: message.to_string() }
126 }
127
128 /// Attach context to an error, mirroring the way the Python implementation
129 /// appends the offending URL to `exn.args`.
130 pub fn context(self, context: impl fmt::Display) -> Self {
131 Error::Context { context: context.to_string(), source: Box::new(self) }
132 }
133}
134
135/// Extension trait for adding context to a [`Result`].
136pub trait ResultExt<T> {
137 /// Attach context lazily to the error branch of a result.
138 fn with_context<C: fmt::Display>(self, f: impl FnOnce() -> C) -> Result<T>;
139}
140
141impl<T, E: Into<Error>> ResultExt<T> for core::result::Result<T, E> {
142 fn with_context<C: fmt::Display>(self, f: impl FnOnce() -> C) -> Result<T> {
143 self.map_err(|e| e.into().context(f()))
144 }
145}