Skip to main content

tendrils_core/
enums.rs

1use serde::{Deserialize, Serialize};
2use std::path::PathBuf;
3
4/// Indicates the tendril action to be performed.
5#[derive(Clone, Debug, Eq, PartialEq)]
6pub enum ActionMode {
7    /// Copy/symlink tendrils from the Tendrils repo to their various locations
8    /// on the computer.
9    Push,
10
11    /// Copy tendrils from their various locations on the computer to the
12    /// Tendrils repo.
13    Pull,
14}
15
16/// Indicates an error while initializing a new
17/// Tendrils repo.
18#[derive(Clone, Debug, Eq, PartialEq)]
19pub enum InitError {
20    /// A general file system error
21    IoError { kind: std::io::ErrorKind },
22
23    /// The folder to initialize is already a
24    /// Tendrils repo
25    AlreadyInitialized,
26
27    /// The folder to initialize is not empty.
28    NotEmpty,
29}
30
31impl From<std::io::Error> for InitError {
32    fn from(err: std::io::Error) -> Self {
33        InitError::IoError { kind: err.kind() }
34    }
35}
36
37impl ToString for InitError {
38    fn to_string(&self) -> String {
39        match self {
40            InitError::IoError { kind: e_kind } => {
41                format!("IO error - {e_kind}")
42            }
43            InitError::AlreadyInitialized => {
44                String::from("This folder is already a Tendrils repo")
45            }
46            InitError::NotEmpty => {
47                String::from(
48                    "This folder is not empty. Creating a Tendrils \
49                    folder here may interfere with the existing \
50                    contents.")
51            }
52        }
53    }
54}
55
56/// Indicates an error while determining the Tendrils
57/// repo to use.
58#[derive(Clone, Debug, Eq, PartialEq)]
59pub enum GetTendrilsRepoError {
60    /// The given path is not a valid Tendrils repo.
61    GivenInvalid { path: PathBuf },
62
63    /// The default Tendrils repo is not a valid Tendrils repo.
64    DefaultInvalid { path: PathBuf },
65
66    /// The default Tendrils repo is not set.
67    DefaultNotSet,
68
69    /// A general error while reading the global configuration file.
70    ConfigError(GetConfigError),
71}
72
73impl ToString for GetTendrilsRepoError {
74    fn to_string(&self) -> String {
75        match self {
76            GetTendrilsRepoError::GivenInvalid { path } => {
77                format!("{} is not a Tendrils repo", path.to_string_lossy())
78            }
79            GetTendrilsRepoError::DefaultInvalid { path } => {
80                format!("The default path \"{}\" is not a Tendrils repo", path.to_string_lossy())
81            }
82            GetTendrilsRepoError::DefaultNotSet => {
83                String::from("The default Tendrils repo path is not set")
84            }
85            GetTendrilsRepoError::ConfigError(err) => err.to_string(),
86        }
87    }
88}
89
90impl From<GetConfigError> for GetTendrilsRepoError {
91    fn from(err: GetConfigError) -> Self {
92        GetTendrilsRepoError::ConfigError(err.with_cfg_type(ConfigType::Global))
93    }
94}
95
96/// Indicates an error while reading/parsing a
97/// configuration file.
98#[derive(Clone, Debug, Eq, PartialEq)]
99pub enum GetConfigError {
100    /// A general file system error while reading the file.
101    IoError { cfg_type: ConfigType, kind: std::io::ErrorKind },
102
103    /// An error while parsing the json from the file.
104    ParseError { cfg_type: ConfigType, msg: String },
105}
106
107impl GetConfigError {
108    /// Copies the error with the updated `cfg_type`
109    pub fn with_cfg_type(self, cfg_type: ConfigType) -> GetConfigError {
110        match self {
111            GetConfigError::IoError { kind, .. } => {
112                GetConfigError::IoError { cfg_type, kind }
113            }
114            GetConfigError::ParseError { msg, .. } => {
115                GetConfigError::ParseError { cfg_type, msg }
116            }
117        }
118    }
119}
120
121impl ToString for GetConfigError {
122    fn to_string(&self) -> String {
123        match self {
124            GetConfigError::IoError { cfg_type, kind } => format!(
125                "IO error while reading the {} file:\n{kind}",
126                cfg_type.file_name(),
127            ),
128            GetConfigError::ParseError { cfg_type, msg } => format!(
129                "Could not parse the {} file:\n{msg}",
130                cfg_type.file_name(),
131            ),
132        }
133    }
134}
135
136impl From<std::io::Error> for GetConfigError {
137    fn from(err: std::io::Error) -> Self {
138        GetConfigError::IoError { cfg_type: ConfigType::Repo, kind: err.kind() }
139    }
140}
141
142impl From<serde_json::Error> for GetConfigError {
143    fn from(err: serde_json::Error) -> Self {
144        GetConfigError::ParseError {
145            cfg_type: ConfigType::Repo,
146            msg: err.to_string()
147        }
148    }
149}
150
151/// Indicates the type of configuration file.
152#[derive(Clone, Debug, Eq, PartialEq)]
153pub enum ConfigType {
154    /// The repo-level `tendrils.json` file
155    Repo,
156
157    /// The `global-config.json` file
158    Global,
159}
160
161impl ConfigType {
162    fn file_name(&self) -> &str {
163        match self {
164            ConfigType::Repo => "tendrils.json",
165            ConfigType::Global => "global-config.json"
166        }
167    }
168}
169
170/// Indicates an error with the setup of a Tendrils repo.
171#[derive(Clone, Debug, Eq, PartialEq)]
172pub enum SetupError {
173    /// The runtime context on Windows does not permit creating symlinks.
174    CannotSymlink,
175    /// An error in importing the configuration.
176    ConfigError(GetConfigError),
177    /// No valid Tendrils repo was found.
178    NoValidTendrilsRepo(GetTendrilsRepoError),
179}
180
181impl ToString for SetupError {
182    fn to_string(&self) -> String {
183        match self {
184            SetupError::CannotSymlink => String::from(
185                "Missing the permissions required to create symlinks on \
186                Windows. Consider:\n    \
187                - Running this command in an elevated terminal\n    \
188                - Enabling developer mode (this allows creating symlinks \
189                without requiring administrator priviledges)\n    \
190                - Changing these tendrils to non-link modes instead"
191            ),
192            SetupError::ConfigError(err) => err.to_string(),
193            SetupError::NoValidTendrilsRepo(err) => err.to_string(),
194        }
195    }
196}
197
198impl From<GetTendrilsRepoError> for SetupError {
199    fn from(err: GetTendrilsRepoError) -> Self {
200        SetupError::NoValidTendrilsRepo(err)
201    }
202}
203
204impl From<GetConfigError> for SetupError {
205    fn from(err: GetConfigError) -> Self {
206        SetupError::ConfigError(err)
207    }
208}
209
210/// Indicates a successful tendril action.
211#[derive(Clone, Debug, Eq, PartialEq)]
212pub enum TendrilActionSuccess {
213    // To keep the memory size of this enum to a minimum, the new and
214    // overwrite variations are separated as their own invariants. If
215    // needed in the future this could become a nested enum, although this
216    // would increase the memory size
217    /// A successful action that created a new file system object at the
218    /// destination.
219    New,
220
221    /// A successful action that overwrote a file system object at the
222    /// destination.
223    Overwrite,
224
225    /// An action that was expected to succeed in creating a new file system
226    /// object at the destination but was skipped due to a dry-run.
227    NewSkipped,
228
229    /// An action that was expected to succeed in overwriting a file system
230    /// object at the destination but was skipped due to a dry-run.
231    OverwriteSkipped,
232}
233
234impl ToString for TendrilActionSuccess {
235    fn to_string(&self) -> String {
236        match self {
237            TendrilActionSuccess::New => String::from("Created"),
238            TendrilActionSuccess::NewSkipped => String::from("Skipped creation"),
239            TendrilActionSuccess::Overwrite => String::from("Overwritten"),
240            TendrilActionSuccess::OverwriteSkipped => {
241                String::from("Skipped overwrite")
242            }
243        }
244    }
245}
246
247/// Indicates an unsuccessful tendril action.
248#[derive(Clone, Debug, Eq, PartialEq)]
249pub enum TendrilActionError {
250    /// General file system errors
251    /// `loc` indicates which side of the action had the unexpected type,
252    /// as indicated by `mistype`
253    IoError {
254        /// The type of error that occured
255        kind: std::io::ErrorKind,
256        /// Where the error occured
257        loc: Location,
258    },
259
260    /// The tendril mode does not match the attempted action, such as:
261    /// - Attempting to pull a link-type tendril
262    /// - Attempting to link a copy-type tendril
263    ModeMismatch,
264
265    /// The type of the remote and local file system objects do not match, or
266    /// do not match the expected types, such as:
267    /// - The source is a file but the destination is a folder
268    /// - The local or remote are symlinks (during a push/pull action)
269    /// - The remote is *not* a symlink (during a link action)
270    TypeMismatch {
271        /// The unexpected type that was found
272        mistype: FsoType,
273        /// Where the unexpected type was found
274        loc: Location,
275    },
276}
277
278impl From<std::io::Error> for TendrilActionError {
279    fn from(err: std::io::Error) -> Self {
280        TendrilActionError::IoError { kind: err.kind(), loc: Location::Unknown }
281    }
282}
283
284impl From<std::io::ErrorKind> for TendrilActionError {
285    fn from(e_kind: std::io::ErrorKind) -> Self {
286        TendrilActionError::IoError { kind: e_kind, loc: Location::Unknown }
287    }
288}
289
290impl ToString for TendrilActionError {
291    fn to_string(&self) -> String {
292        use std::io::ErrorKind::NotFound;
293        use FsoType::{Dir, File, SymDir, SymFile, BrokenSym};
294        use Location::{Dest, Source, Unknown};
295        match self {
296            TendrilActionError::IoError { kind: NotFound, loc: Source } => {
297                String::from("Source not found")
298            }
299            TendrilActionError::IoError { kind: NotFound, loc: Dest } => {
300                String::from("Destination not found")
301            }
302            TendrilActionError::IoError { kind: NotFound, loc: Unknown } => {
303                String::from("Not found")
304            }
305            TendrilActionError::IoError { kind: e_kind, loc: Source } => {
306                format!("{:?} error at source", e_kind)
307            }
308            TendrilActionError::IoError { kind: e_kind, loc: Dest } => {
309                format!("{:?} error at destination", e_kind)
310            }
311            TendrilActionError::IoError { kind: e_kind, loc: Unknown } => {
312                format!("{:?} error", e_kind)
313            }
314            TendrilActionError::ModeMismatch => {
315                String::from("Wrong tendril type")
316            }
317            TendrilActionError::TypeMismatch { loc: Source, mistype: File } => {
318                String::from("Unexpected file at source")
319            }
320            TendrilActionError::TypeMismatch { loc: Source, mistype: Dir } => {
321                String::from("Unexpected directory at source")
322            }
323            TendrilActionError::TypeMismatch {
324                loc: Source,
325                mistype: SymFile | SymDir | BrokenSym,
326            } => String::from("Unexpected symlink at source"),
327            TendrilActionError::TypeMismatch { loc: Dest, mistype: File } => {
328                String::from("Unexpected file at destination")
329            }
330            TendrilActionError::TypeMismatch { loc: Dest, mistype: Dir } => {
331                String::from("Unexpected directory at destination")
332            }
333            TendrilActionError::TypeMismatch {
334                loc: Dest,
335                mistype: SymFile | SymDir | BrokenSym,
336            } => String::from("Unexpected symlink at destination"),
337            TendrilActionError::TypeMismatch { loc: Unknown, mistype: _ } => {
338                String::from("Unexpected file system object")
339            }
340        }
341    }
342}
343
344/// Indicates a side of a file system transaction
345#[derive(Clone, Debug, Eq, PartialEq)]
346pub enum Location {
347    Source,
348    Dest,
349    Unknown,
350}
351
352/// Indicates a type of file system object
353#[derive(Clone, Debug, Eq, PartialEq)]
354pub enum FsoType {
355    /// A standard file
356    File,
357    /// A standard directory
358    Dir,
359    /// A symlink to a file
360    SymFile,
361    /// A symlink to a directory
362    SymDir,
363    /// A symlink who's target does not exist or is not available.
364    BrokenSym,
365}
366
367impl FsoType {
368    pub fn is_file(&self) -> bool {
369        self == &FsoType::File || self == &FsoType::SymFile
370    }
371
372    pub fn is_dir(&self) -> bool {
373        self == &FsoType::Dir || self == &FsoType::SymDir
374    }
375
376    pub fn is_symlink(&self) -> bool {
377        self == &FsoType::SymFile
378        || self == &FsoType::SymDir
379        || self == &FsoType::BrokenSym
380    }
381}
382
383/// Indicates the behaviour of this tendril, and determines whether it is
384/// a copy-type, or a link-type tendril.
385#[derive(Copy, Clone, Debug, PartialEq, Eq)]
386pub enum TendrilMode {
387    /// Overwrite any files/folders that are present in both the source and
388    /// destination, but keep anything in the destination folder that is not
389    /// in the source folder. This only applies to folder tendrils.
390    /// Tendrils with this mode are considered copy-type.
391    CopyMerge,
392
393    /// Completely overwrite the destination folder with the contents of
394    /// the source folder. This only applies to folder tendrils.
395    /// Tendrils with this mode are considered copy-type.
396    CopyOverwrite,
397
398    /// Create a symlink at the remote location that points to local
399    /// file/folder.
400    Link,
401}
402
403impl TendrilMode {
404    pub fn requires_symlink(&self) -> bool {
405        *self == TendrilMode::Link
406    }
407}
408
409impl ToString for TendrilMode {
410    fn to_string(&self) -> String {
411        match &self {
412            TendrilMode::CopyMerge => String::from("Copy - Directory merge"),
413            TendrilMode::CopyOverwrite => String::from("Copy - Directory overwrite"),
414            TendrilMode::Link => String::from("Link"),
415        }
416    }
417}
418
419/// Indicates an invalid tendril field.
420#[derive(Clone, Debug, Eq, PartialEq)]
421pub enum InvalidTendrilError {
422    InvalidLocal,
423
424    /// The tendril remote conflicts with the Tendrils repo. This can occur if:
425    /// - Including the Tendrils repo as a tendril
426    /// - A folder tendril is an ancestor to the Tendrils repo
427    /// - A tendril is inside the Tendrils repo
428    Recursion,
429}
430
431#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
432#[serde(untagged)]
433pub(crate) enum OneOrMany<T> {
434    // https://github.com/Mingun/ksc-rs/blob/8532f701e660b07b6d2c74963fdc0490be4fae4b/src/parser.rs#L29pub
435    /// Single value
436    One(T),
437
438    /// Array of values
439    Vec(Vec<T>),
440}
441
442impl<T> From<OneOrMany<T>> for Vec<T> {
443    fn from(from: OneOrMany<T>) -> Self {
444        match from {
445            OneOrMany::One(val) => vec![val],
446            OneOrMany::Vec(vec) => vec,
447        }
448    }
449}
450
451impl<T> From<Vec<T>> for OneOrMany<T> {
452    fn from(mut from: Vec<T>) -> Self {
453        match from.len() {
454            1 => OneOrMany::One(from.remove(0)),
455            _ => OneOrMany::Vec(from),
456        }
457    }
458}