Skip to main content

dodot_lib/
error.rs

1use std::path::PathBuf;
2use thiserror::Error;
3
4/// The single error type for all dodot operations.
5///
6/// Each variant carries enough context to produce a useful error message
7/// without needing to inspect the source chain.
8#[derive(Error, Debug)]
9#[non_exhaustive]
10pub enum DodotError {
11    #[error("filesystem error at {path}: {source}")]
12    Fs {
13        path: PathBuf,
14        source: std::io::Error,
15    },
16
17    #[error("symlink conflict: {path} already exists and is not managed by dodot")]
18    SymlinkConflict { path: PathBuf },
19
20    #[error("protected path: {path} cannot be symlinked")]
21    ProtectedPath { path: PathBuf },
22
23    #[error("pack not found: {name}")]
24    PackNotFound { name: String },
25
26    #[error("pack is invalid: {name}: {reason}")]
27    PackInvalid { name: String, reason: String },
28
29    #[error("pack ordering collision: display name `{display_name}` resolves to multiple packs:\n{}", .paths.iter().map(|p| format!("  - {}", p.display())).collect::<Vec<_>>().join("\n"))]
30    PackOrderingCollision {
31        display_name: String,
32        paths: Vec<PathBuf>,
33    },
34
35    #[error("handler not found: {name}")]
36    HandlerNotFound { name: String },
37
38    #[error("config error: {0}")]
39    Config(String),
40
41    #[error("command failed: {command} (exit code {exit_code})\n{stderr}")]
42    CommandFailed {
43        command: String,
44        exit_code: i32,
45        stderr: String,
46    },
47
48    #[error("invalid pattern {pattern}: {reason}")]
49    InvalidPattern { pattern: String, reason: String },
50
51    #[error("cross-pack deployment conflict detected (--force does not override this):\n{}", crate::conflicts::format_conflicts(.conflicts))]
52    CrossPackConflict {
53        conflicts: Vec<crate::conflicts::Conflict>,
54    },
55
56    #[error("preprocessing failed for {source_file} ({preprocessor}): {message}")]
57    PreprocessorError {
58        preprocessor: String,
59        source_file: PathBuf,
60        message: String,
61    },
62
63    #[error("preprocessing collision in pack \"{pack}\": {source_file} expands to {expanded_name}, which conflicts with an existing pack file or another preprocessor's output")]
64    PreprocessorCollision {
65        pack: String,
66        source_file: String,
67        expanded_name: String,
68    },
69
70    #[error("template render failed for {}:\n  {message}", source_file.display())]
71    TemplateRender {
72        source_file: PathBuf,
73        message: String,
74    },
75
76    #[error("template variable name \"{name}\" is reserved (dodot and env are built-in namespaces); choose a different name in [preprocessor.template.vars]")]
77    TemplateReservedVar { name: String },
78
79    #[error("{0}")]
80    Other(String),
81}
82
83/// Convenience alias used throughout the crate.
84pub type Result<T> = std::result::Result<T, DodotError>;
85
86/// Helper to wrap an `io::Error` with the path that caused it.
87pub(crate) fn fs_err(path: impl Into<PathBuf>, source: std::io::Error) -> DodotError {
88    DodotError::Fs {
89        path: path.into(),
90        source,
91    }
92}