1use std::path::Path;
8
9use serde::{Deserialize, Serialize};
10
11#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)]
13#[serde(rename_all = "snake_case")]
14pub enum HelperError {
15 #[error("{0}")]
17 Validation(String),
18
19 #[error("{path} is a symlink to {current} (not ArcBox-owned, not replacing)")]
21 ForeignSymlink { path: String, current: String },
22
23 #[error("{path} exists and is not a symlink (not replacing)")]
25 NotASymlink { path: String },
26
27 #[error("CLI target '{target}' is a symlink (refusing to link through it)")]
29 CliTargetIsSymlink { target: String },
30
31 #[error("CLI target does not exist: {target}")]
33 CliTargetMissing { target: String },
34
35 #[error("CLI target '{target}' is not a regular file")]
37 CliTargetNotFile { target: String },
38
39 #[error("CLI target '{target}' resolves to {resolved} which is not an ArcBox xbin path")]
41 CliTargetEscaped { target: String, resolved: String },
42
43 #[error("{path} exists and is not ArcBox-managed (not overwriting)")]
45 ForeignManagedFile { path: String },
46
47 #[error("{op} {path}: {detail}")]
49 Io {
50 op: String,
51 path: String,
52 detail: String,
53 },
54
55 #[error("{0}")]
59 Other(String),
60
61 #[error("{path} exists but is not a symlink (is Docker Desktop running? stop it first)")]
67 DockerSocketOccupied { path: String },
68}
69
70impl HelperError {
71 #[must_use]
72 pub fn validation(msg: impl Into<String>) -> Self {
73 Self::Validation(msg.into())
74 }
75
76 #[must_use]
77 pub fn other(msg: impl Into<String>) -> Self {
78 Self::Other(msg.into())
79 }
80
81 #[must_use]
82 pub fn io(op: &str, path: impl AsRef<Path>, err: impl std::fmt::Display) -> Self {
83 Self::Io {
84 op: op.to_owned(),
85 path: path.as_ref().display().to_string(),
86 detail: err.to_string(),
87 }
88 }
89
90 #[must_use]
91 pub fn foreign_symlink(path: impl AsRef<Path>, current: impl AsRef<Path>) -> Self {
92 Self::ForeignSymlink {
93 path: path.as_ref().display().to_string(),
94 current: current.as_ref().display().to_string(),
95 }
96 }
97
98 #[must_use]
99 pub fn not_a_symlink(path: impl AsRef<Path>) -> Self {
100 Self::NotASymlink {
101 path: path.as_ref().display().to_string(),
102 }
103 }
104
105 #[must_use]
106 pub fn foreign_managed(path: impl AsRef<Path>) -> Self {
107 Self::ForeignManagedFile {
108 path: path.as_ref().display().to_string(),
109 }
110 }
111
112 #[must_use]
113 pub fn docker_socket_occupied(path: impl AsRef<Path>) -> Self {
114 Self::DockerSocketOccupied {
115 path: path.as_ref().display().to_string(),
116 }
117 }
118}
119
120impl HelperError {
122 #[must_use]
123 pub fn code(&self) -> &'static str {
124 match self {
125 Self::Validation(_) => "validation",
126 Self::ForeignSymlink { .. } => "foreign_symlink",
127 Self::NotASymlink { .. } => "not_a_symlink",
128 Self::CliTargetIsSymlink { .. } => "cli_target_is_symlink",
129 Self::CliTargetMissing { .. } => "cli_target_missing",
130 Self::CliTargetNotFile { .. } => "cli_target_not_file",
131 Self::CliTargetEscaped { .. } => "cli_target_escaped",
132 Self::ForeignManagedFile { .. } => "foreign_managed_file",
133 Self::Io { .. } => "io",
134 Self::Other(_) => "other",
135 Self::DockerSocketOccupied { .. } => "docker_socket_occupied",
136 }
137 }
138}