Skip to main content

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}