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 #[error("route conflict for {subnet}")]
71 RouteConflict { subnet: String },
72}
73
74impl HelperError {
75 #[must_use]
76 pub fn validation(msg: impl Into<String>) -> Self {
77 Self::Validation(msg.into())
78 }
79
80 #[must_use]
81 pub fn other(msg: impl Into<String>) -> Self {
82 Self::Other(msg.into())
83 }
84
85 #[must_use]
86 pub fn io(op: &str, path: impl AsRef<Path>, err: impl std::fmt::Display) -> Self {
87 Self::Io {
88 op: op.to_owned(),
89 path: path.as_ref().display().to_string(),
90 detail: err.to_string(),
91 }
92 }
93
94 #[must_use]
95 pub fn foreign_symlink(path: impl AsRef<Path>, current: impl AsRef<Path>) -> Self {
96 Self::ForeignSymlink {
97 path: path.as_ref().display().to_string(),
98 current: current.as_ref().display().to_string(),
99 }
100 }
101
102 #[must_use]
103 pub fn not_a_symlink(path: impl AsRef<Path>) -> Self {
104 Self::NotASymlink {
105 path: path.as_ref().display().to_string(),
106 }
107 }
108
109 #[must_use]
110 pub fn foreign_managed(path: impl AsRef<Path>) -> Self {
111 Self::ForeignManagedFile {
112 path: path.as_ref().display().to_string(),
113 }
114 }
115
116 #[must_use]
117 pub fn docker_socket_occupied(path: impl AsRef<Path>) -> Self {
118 Self::DockerSocketOccupied {
119 path: path.as_ref().display().to_string(),
120 }
121 }
122}
123
124impl HelperError {
126 #[must_use]
127 pub fn code(&self) -> &'static str {
128 match self {
129 Self::Validation(_) => "validation",
130 Self::ForeignSymlink { .. } => "foreign_symlink",
131 Self::NotASymlink { .. } => "not_a_symlink",
132 Self::CliTargetIsSymlink { .. } => "cli_target_is_symlink",
133 Self::CliTargetMissing { .. } => "cli_target_missing",
134 Self::CliTargetNotFile { .. } => "cli_target_not_file",
135 Self::CliTargetEscaped { .. } => "cli_target_escaped",
136 Self::ForeignManagedFile { .. } => "foreign_managed_file",
137 Self::Io { .. } => "io",
138 Self::Other(_) => "other",
139 Self::DockerSocketOccupied { .. } => "docker_socket_occupied",
140 Self::RouteConflict { .. } => "route_conflict",
141 }
142 }
143}