1use miette::Diagnostic;
2use soar_utils::error::{PathError, UtilsError};
3use thiserror::Error;
4
5#[derive(Error, Diagnostic, Debug)]
6pub enum ConfigError {
7 #[error(transparent)]
8 #[diagnostic(
9 code(soar_config::toml_serialize),
10 help("Check your configuration structure for invalid values")
11 )]
12 TomlSerError(#[from] toml::ser::Error),
13
14 #[error(transparent)]
15 #[diagnostic(
16 code(soar_config::toml_deserialize),
17 help("Check your config.toml syntax and structure")
18 )]
19 TomlDeError(#[from] toml::de::Error),
20
21 #[error("Configuration file already exists")]
22 #[diagnostic(
23 code(soar_config::already_exists),
24 help("Remove the existing config file or use a different location")
25 )]
26 ConfigAlreadyExists,
27
28 #[error("Packages configuration file not found: {0}")]
29 #[diagnostic(
30 code(soar_config::packages_not_found),
31 help("Create a packages.toml file or run `soar defpackages` to generate one")
32 )]
33 PackagesConfigNotFound(String),
34
35 #[error("Packages configuration file already exists")]
36 #[diagnostic(
37 code(soar_config::packages_already_exists),
38 help("Remove the existing packages.toml file or use a different location")
39 )]
40 PackagesConfigAlreadyExists,
41
42 #[error("Invalid profile: {0}")]
43 #[diagnostic(
44 code(soar_config::invalid_profile),
45 help("Check available profiles in your config file")
46 )]
47 InvalidProfile(String),
48
49 #[error("Missing default profile: {0}")]
50 #[diagnostic(
51 code(soar_config::missing_default_profile),
52 help("Ensure the default_profile field references an existing profile")
53 )]
54 MissingDefaultProfile(String),
55
56 #[error("Missing profile: {0}")]
57 #[diagnostic(
58 code(soar_config::missing_profile),
59 help("Add the profile to your configuration or use an existing one")
60 )]
61 MissingProfile(String),
62
63 #[error("Repository '{0}' is not configured")]
64 #[diagnostic(
65 code(soar_config::repository_not_found),
66 help("Run `soar repo list` to see the configured repositories.")
67 )]
68 RepositoryNotFound(String),
69
70 #[error("Repository name '{0}' is not a valid directory name")]
71 #[diagnostic(
72 code(soar_config::invalid_repository_name),
73 help(
74 "A repository's data is stored in a directory named after it, under the \
75 repositories path, so the name must be a single path component. It cannot be \
76 empty, contain '/', be '.' or '..', or be an absolute path. Try a plain name, \
77 e.g. `soar repo add personal https://personal.repo`."
78 )
79 )]
80 InvalidRepositoryName(String),
81
82 #[error("Invalid repository URL: {0}")]
83 #[diagnostic(code(soar_config::invalid_repository_url))]
84 InvalidRepositoryUrl(String),
85
86 #[error("Reserved repository name 'local' cannot be used")]
87 #[diagnostic(
88 code(soar_config::reserved_repo_name),
89 help("Choose a different name for your repository")
90 )]
91 ReservedRepositoryName,
92
93 #[error("Duplicate repository name: {0}")]
94 #[diagnostic(
95 code(soar_config::duplicate_repo),
96 help("Each repository must have a unique name")
97 )]
98 DuplicateRepositoryName(String),
99
100 #[error("Repository '{0}' has signature verification enabled but no pubkey configured")]
101 #[diagnostic(
102 code(soar_config::missing_pubkey),
103 help("Provide a pubkey for the repository or disable signature_verification")
104 )]
105 MissingPubkey(String),
106
107 #[error(transparent)]
108 #[diagnostic(code(soar_config::io))]
109 IoError(#[from] std::io::Error),
110
111 #[error(transparent)]
112 #[diagnostic(code(soar_config::utils))]
113 Utils(#[from] soar_utils::error::UtilsError),
114
115 #[error(transparent)]
116 #[diagnostic(code(soar_config::toml))]
117 Toml(#[from] toml_edit::TomlError),
118
119 #[error("Encountered unexpected TOML item: {0}")]
120 #[diagnostic(code(soar_config::unexpected_toml_item))]
121 UnexpectedTomlItem(String),
122
123 #[error("Failed to annotate first table in array: {0}")]
124 #[diagnostic(code(soar_config::annotate_first_table))]
125 AnnotateFirstTable(String),
126
127 #[error("{0}")]
128 #[diagnostic(code(soar_config::custom))]
129 Custom(String),
130}
131
132impl From<PathError> for ConfigError {
133 fn from(err: PathError) -> Self {
134 Self::Utils(UtilsError::Path(err))
135 }
136}
137
138impl From<soar_utils::error::BytesError> for ConfigError {
139 fn from(err: soar_utils::error::BytesError) -> Self {
140 Self::Utils(UtilsError::Bytes(err))
141 }
142}
143
144impl From<soar_utils::error::FileSystemError> for ConfigError {
145 fn from(err: soar_utils::error::FileSystemError) -> Self {
146 Self::Utils(UtilsError::FileSystem(err))
147 }
148}
149
150pub type Result<T> = std::result::Result<T, ConfigError>;
151
152#[cfg(test)]
153mod tests {
154 use super::*;
155
156 #[test]
157 fn test_error_display() {
158 let err = ConfigError::ConfigAlreadyExists;
159 assert_eq!(err.to_string(), "Configuration file already exists");
160
161 let err = ConfigError::InvalidProfile("test".to_string());
162 assert_eq!(err.to_string(), "Invalid profile: test");
163
164 let err = ConfigError::DuplicateRepositoryName("duplicate".to_string());
165 assert_eq!(err.to_string(), "Duplicate repository name: duplicate");
166 }
167
168 #[test]
169 fn test_error_from_conversions() {
170 let path_err = soar_utils::error::PathError::MissingEnvVar {
171 var: "SOAR_ROOT".to_string(),
172 input: "$SOR_ROOT/test".to_string(),
173 };
174 let config_err: ConfigError = path_err.into();
175 assert!(matches!(config_err, ConfigError::Utils(_)));
176 }
177}