1use std::path::PathBuf;
2use thiserror::Error;
3
4#[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(
57 "routing override conflict in pack `{pack}` for `{rel_path}`:\n \
58 filename routes via its prefix, and `[symlink.targets]` declares `{config_target}`.\n \
59 pick one — either rename the file (drop the `home.`/`app.`/`xdg.`/`lib.` or `_home/`/`_xdg/`/`_app/`/`_lib/` prefix) \
60 or remove the `[symlink.targets]` entry."
61 )]
62 RoutingOverrideConflict {
63 pack: String,
64 rel_path: String,
65 config_target: String,
66 },
67
68 #[error("preprocessing failed for {source_file} ({preprocessor}): {message}")]
69 PreprocessorError {
70 preprocessor: String,
71 source_file: PathBuf,
72 message: String,
73 },
74
75 #[error("preprocessing collision in pack \"{pack}\": {source_file} expands to {expanded_name}, which conflicts with an existing pack file or another preprocessor's output")]
76 PreprocessorCollision {
77 pack: String,
78 source_file: String,
79 expanded_name: String,
80 },
81
82 #[error("template render failed for {}:\n {message}", source_file.display())]
83 TemplateRender {
84 source_file: PathBuf,
85 message: String,
86 },
87
88 #[error("template variable name \"{name}\" is reserved (dodot and env are built-in namespaces); choose a different name in [preprocessor.template.vars]")]
89 TemplateReservedVar { name: String },
90
91 #[error("unresolved dodot-conflict markers in {} at line{} {}\n resolve the conflict block(s) with `git diff -- '{}'` and remove the dodot-conflict marker lines, then re-run.", source_file.display(), if line_numbers.len() == 1 { "" } else { "s" }, line_numbers.iter().map(|n| n.to_string()).collect::<Vec<_>>().join(", "), source_file.display())]
97 UnresolvedConflictMarker {
98 source_file: PathBuf,
99 line_numbers: Vec<usize>,
100 },
101
102 #[error("{0}")]
103 Other(String),
104}
105
106pub type Result<T> = std::result::Result<T, DodotError>;
108
109pub(crate) fn fs_err(path: impl Into<PathBuf>, source: std::io::Error) -> DodotError {
111 DodotError::Fs {
112 path: path.into(),
113 source,
114 }
115}