Skip to main content

soar_config/
error.rs

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}