Skip to main content

virtual_file_transfer/
error.rs

1use std::fmt;
2use std::io;
3
4/// Result type used by this crate's public fallible operations.
5pub type Result<T> = std::result::Result<T, Error>;
6
7/// An error returned by a public transfer or file-definition operation.
8///
9/// Input errors describe invalid file names or transfer requests. Provider
10/// errors originate in I/O operations, while platform errors describe
11/// operating-system integration failures.
12#[non_exhaustive]
13#[derive(Debug)]
14pub enum Error {
15    /// Invalid file name, path, or transfer request.
16    InvalidInput(InputError),
17    /// An I/O error originating from a content provider.
18    Provider(io::Error),
19    /// An error from platform initialization or transfer integration.
20    Platform(PlatformError),
21}
22
23/// The reason a virtual file name or path component is invalid.
24///
25/// Windows-specific restrictions include reserved device names, prohibited
26/// characters, and the UTF-16 capacity of `FILEDESCRIPTORW`.
27#[non_exhaustive]
28#[derive(Debug, Clone, PartialEq, Eq)]
29pub enum FileNameErrorReason {
30    /// A prohibited character was found in a path component.
31    InvalidCharacter(char),
32    /// A path component is invalid, such as an empty or traversal component.
33    InvalidComponent(String),
34    /// A path component uses a reserved Windows device name.
35    ReservedName(String),
36    /// A Windows path component ends with a space or period.
37    TrailingSpaceOrPeriod(String),
38    /// The file path exceeds the supported length in UTF-16 code units.
39    ///
40    /// `actual` is the encoded length and `max` is the supported maximum.
41    TooLong { actual: usize, max: usize },
42}
43
44/// An invalid file definition or transfer request supplied by the caller.
45#[non_exhaustive]
46#[derive(Debug, Clone, PartialEq, Eq)]
47pub enum InputError {
48    /// A file path conflicts with another path treated as a parent directory.
49    ConflictPath,
50    /// Two files in the same transfer have duplicate destination paths.
51    DuplicatePath,
52    /// A virtual file was constructed with an empty name.
53    EmptyName,
54    /// A transfer request contains no virtual files.
55    EmptyTransfer,
56    /// A file name or relative path violates validation rules.
57    InvalidFileName {
58        /// The rejected name or relative path.
59        name: String,
60        /// The validation rule that was violated.
61        reason: FileNameErrorReason,
62    },
63}
64
65/// The category of an operating-system integration error.
66#[non_exhaustive]
67#[derive(Debug, Clone, Copy, PartialEq, Eq)]
68pub enum PlatformErrorKind {
69    /// The platform cannot represent or accept a file name.
70    UnsupportedFileName,
71    /// Initialization of required platform facilities failed.
72    InitializationFailed,
73    /// A platform transfer or clipboard operation failed.
74    TransferFailed,
75}
76
77/// A platform-specific failure with a categorized kind and diagnostic message.
78///
79/// Use [`Self::kind`] to inspect the category and [`Self::message`] for
80/// additional details supplied by the platform adapter.
81#[derive(Debug)]
82pub struct PlatformError {
83    /// Returns the category of this platform error.
84    kind: PlatformErrorKind,
85    /// Returns the diagnostic message describing this platform error.
86    message: String,
87}
88
89impl PlatformError {
90    pub(crate) fn new(kind: PlatformErrorKind, message: impl Into<String>) -> Self {
91        Self {
92            kind,
93            message: message.into(),
94        }
95    }
96
97    pub fn kind(&self) -> PlatformErrorKind {
98        self.kind
99    }
100
101    pub fn message(&self) -> &str {
102        &self.message
103    }
104}
105
106impl From<io::Error> for Error {
107    fn from(error: io::Error) -> Self {
108        Self::Provider(error)
109    }
110}
111
112impl fmt::Display for InputError {
113    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
114        match self {
115            Self::EmptyName => write!(f, "file name must not be empty"),
116            Self::InvalidFileName { name, reason } => {
117                write!(f, "invalid file name {name:?}: {reason}")
118            }
119            Self::EmptyTransfer => write!(f, "transfer must contain at least one file"),
120            Self::DuplicatePath => write!(f, "duplicate file path"),
121            Self::ConflictPath => write!(f, "file path conflicts with a parent path"),
122        }
123    }
124}
125
126impl fmt::Display for FileNameErrorReason {
127    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128        match self {
129            Self::InvalidCharacter(ch) => write!(f, "invalid character {ch:?}"),
130            Self::InvalidComponent(component) => write!(f, "invalid path component {component:?}"),
131            Self::ReservedName(component) => write!(f, "reserved device name {component:?}"),
132            Self::TrailingSpaceOrPeriod(component) => {
133                write!(f, "trailing space or period in {component:?}")
134            }
135            Self::TooLong { actual, max } => {
136                write!(
137                    f,
138                    "path length {actual} exceeds maximum {max} UTF-16 code units"
139                )
140            }
141        }
142    }
143}
144
145impl fmt::Display for PlatformError {
146    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
147        write!(f, "{}", self.message)
148    }
149}
150
151impl fmt::Display for Error {
152    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
153        match self {
154            Self::InvalidInput(error) => {
155                write!(f, "invalid input: {error}")
156            }
157
158            Self::Provider(error) => {
159                write!(f, "provider error: {error}")
160            }
161
162            Self::Platform(error) => {
163                write!(f, "platform error: {error}")
164            }
165        }
166    }
167}
168
169impl std::error::Error for Error {
170    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
171        match self {
172            Self::Provider(error) => Some(error),
173            _ => None,
174        }
175    }
176}