Skip to main content

es_fluent_shared/
error.rs

1//! Common error types shared across the es-fluent ecosystem.
2
3use std::path::PathBuf;
4use thiserror::Error;
5use unic_langid::LanguageIdentifier;
6
7/// Common error types shared across the es-fluent ecosystem.
8#[derive(Debug, Error)]
9pub enum EsFluentError {
10    /// Configuration file not found.
11    #[error("Configuration file not found: {path}")]
12    ConfigNotFound { path: PathBuf },
13
14    /// Failed to parse configuration file.
15    #[error("Failed to parse configuration file: {0}")]
16    ConfigParseError(#[from] toml::de::Error),
17
18    /// Assets directory not found.
19    #[error("Assets directory not found: {path}")]
20    AssetsNotFound { path: PathBuf },
21
22    /// Fallback language directory not found.
23    #[error("Fallback language directory not found: {language}")]
24    FallbackLanguageNotFound { language: String },
25
26    /// Invalid language identifier.
27    #[error("Invalid language identifier '{identifier}': {reason}")]
28    InvalidLanguageIdentifier { identifier: String, reason: String },
29
30    /// Language not supported.
31    #[error("Language '{0}' is not supported")]
32    LanguageNotSupported(LanguageIdentifier),
33
34    /// Fluent parsing error.
35    #[error("Fluent parsing error: {0:?}")]
36    FluentParseError(Vec<fluent_syntax::parser::ParserError>),
37
38    /// Fluent serialization error.
39    #[error("Fluent serialization error: {0}")]
40    FluentSerializeError(#[from] std::fmt::Error),
41
42    /// IO error during file operations.
43    #[error("IO error: {0}")]
44    IoError(#[from] std::io::Error),
45
46    /// Environment variable error.
47    #[error("Environment variable error: {0}")]
48    EnvVarError(#[from] std::env::VarError),
49
50    /// Generic backend error.
51    #[error("Backend error: {0}")]
52    BackendError(#[from] anyhow::Error),
53
54    /// Missing package name.
55    #[error("Missing package name")]
56    MissingPackageName,
57}
58
59impl EsFluentError {
60    /// Creates a configuration not found error.
61    pub fn config_not_found(path: impl Into<PathBuf>) -> Self {
62        Self::ConfigNotFound { path: path.into() }
63    }
64
65    /// Creates an assets not found error.
66    pub fn assets_not_found(path: impl Into<PathBuf>) -> Self {
67        Self::AssetsNotFound { path: path.into() }
68    }
69
70    /// Creates an invalid language identifier error.
71    pub fn invalid_language_identifier(
72        identifier: impl Into<String>,
73        reason: impl Into<String>,
74    ) -> Self {
75        Self::InvalidLanguageIdentifier {
76            identifier: identifier.into(),
77            reason: reason.into(),
78        }
79    }
80
81    /// Creates a fallback language not found error.
82    pub fn fallback_language_not_found(language: impl Into<String>) -> Self {
83        Self::FallbackLanguageNotFound {
84            language: language.into(),
85        }
86    }
87}
88
89/// A result type for common es-fluent operations.
90pub type EsFluentResult<T> = Result<T, EsFluentError>;
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95    use std::error::Error;
96
97    #[test]
98    fn helper_constructors_build_expected_variants() {
99        let config = EsFluentError::config_not_found("/tmp/i18n.toml");
100        assert!(matches!(config, EsFluentError::ConfigNotFound { .. }));
101
102        let assets = EsFluentError::assets_not_found("/tmp/i18n");
103        assert!(matches!(assets, EsFluentError::AssetsNotFound { .. }));
104
105        let invalid = EsFluentError::invalid_language_identifier("bad", "parse failure");
106        assert!(matches!(
107            invalid,
108            EsFluentError::InvalidLanguageIdentifier { .. }
109        ));
110
111        let fallback = EsFluentError::fallback_language_not_found("en-US");
112        assert!(matches!(
113            fallback,
114            EsFluentError::FallbackLanguageNotFound { .. }
115        ));
116    }
117
118    #[test]
119    fn display_messages_include_variant_context() {
120        let supported = EsFluentError::LanguageNotSupported("fr".parse().unwrap());
121        assert_eq!(supported.to_string(), "Language 'fr' is not supported");
122
123        let fluent = EsFluentError::FluentParseError(Vec::new());
124        assert_eq!(fluent.to_string(), "Fluent parsing error: []");
125
126        let serialize = EsFluentError::FluentSerializeError(std::fmt::Error);
127        assert_eq!(
128            serialize.to_string(),
129            "Fluent serialization error: an error occurred when formatting an argument"
130        );
131
132        let missing = EsFluentError::MissingPackageName;
133        assert_eq!(missing.to_string(), "Missing package name");
134    }
135
136    #[test]
137    fn source_errors_are_preserved_for_wrapped_variants() {
138        let parse_error: EsFluentError = toml::from_str::<toml::Value>("=").unwrap_err().into();
139        assert!(parse_error.source().is_some());
140
141        let io_error: EsFluentError =
142            std::io::Error::new(std::io::ErrorKind::NotFound, "missing").into();
143        assert!(io_error.source().is_some());
144
145        let env_error: EsFluentError = std::env::VarError::NotPresent.into();
146        assert!(env_error.source().is_some());
147
148        let backend_error: EsFluentError = anyhow::anyhow!("backend").into();
149        assert!(backend_error.source().is_some());
150    }
151}