Skip to main content

es_fluent_derive_core/
error.rs

1//! This module provides the error types for `es-fluent-derive-core`.
2
3use proc_macro_error2::{abort, abort_call_site, emit_error};
4use proc_macro2::Span;
5use std::path::PathBuf;
6use thiserror::Error;
7use unic_langid::LanguageIdentifier;
8
9/// An error that can occur when parsing `es-fluent` attributes.
10#[derive(Clone, Debug, thiserror::Error)]
11pub enum EsFluentCoreError {
12    /// An error related to Fluent attribute parsing.
13    #[error("Attribute error: {message}")]
14    AttributeError { message: String, span: Option<Span> },
15
16    /// An error related to variant consistency.
17    #[error("Variant '{variant_name}' error: {message}")]
18    VariantError {
19        message: String,
20        variant_name: String,
21        span: Option<Span>,
22    },
23
24    /// An error related to field processing.
25    #[error("{}", field_error_fmt(.message, .field_name))]
26    FieldError {
27        message: String,
28        field_name: Option<String>,
29        span: Option<Span>,
30    },
31
32    /// An error with conversions or transformations.
33    #[error("Transform error: {message}")]
34    TransformError { message: String, span: Option<Span> },
35}
36
37fn field_error_fmt(message: &str, field_name: &Option<String>) -> String {
38    match field_name {
39        Some(name) => format!("Field '{}' error: {}", name, message),
40        None => format!("Field error: {}", message),
41    }
42}
43
44impl EsFluentCoreError {
45    /// Returns the span of the error.
46    pub fn span(&self) -> Option<Span> {
47        match self {
48            EsFluentCoreError::AttributeError { span, .. } => *span,
49            EsFluentCoreError::VariantError { span, .. } => *span,
50            EsFluentCoreError::FieldError { span, .. } => *span,
51            EsFluentCoreError::TransformError { span, .. } => *span,
52        }
53    }
54
55    /// Returns a mutable reference to the error message.
56    pub fn message_mut(&mut self) -> &mut String {
57        match self {
58            EsFluentCoreError::AttributeError { message, .. } => message,
59            EsFluentCoreError::VariantError { message, .. } => message,
60            EsFluentCoreError::FieldError { message, .. } => message,
61            EsFluentCoreError::TransformError { message, .. } => message,
62        }
63    }
64
65    /// Aborts the macro execution with the error.
66    pub fn abort(self) -> ! {
67        let msg = self.to_string();
68        match self.span() {
69            Some(span) => abort!(span, "{}", msg),
70            None => abort_call_site!("{}", msg),
71        }
72    }
73
74    /// Emits the error as a compiler error.
75    pub fn emit(&self) {
76        let msg = self.to_string();
77        match self.span() {
78            Some(span) => emit_error!(span, "{}", msg),
79            None => emit_error!("{}", msg),
80        }
81    }
82}
83
84/// A trait for adding notes and help messages to an error.
85pub trait ErrorExt {
86    /// Adds a note to the error.
87    fn with_note(self, note: String) -> Self;
88    /// Adds a help message to the error.
89    fn with_help(self, help: String) -> Self;
90}
91
92impl ErrorExt for EsFluentCoreError {
93    fn with_note(mut self, note_msg: String) -> Self {
94        let message = self.message_mut();
95        *message = format!("{}\nnote: {}", message, note_msg);
96        self
97    }
98
99    fn with_help(mut self, help_msg: String) -> Self {
100        let message = self.message_mut();
101        *message = format!("{}\nhelp: {}", message, help_msg);
102        self
103    }
104}
105
106/// A result type for `es-fluent-derive-core`.
107pub type EsFluentCoreResult<T> = Result<T, EsFluentCoreError>;
108
109/// Common error types shared across the es-fluent ecosystem.
110#[derive(Debug, Error)]
111pub enum EsFluentError {
112    /// Configuration file not found.
113    #[error("Configuration file not found: {path}")]
114    ConfigNotFound { path: PathBuf },
115
116    /// Failed to parse configuration file.
117    #[error("Failed to parse configuration file: {0}")]
118    ConfigParseError(#[from] toml::de::Error),
119
120    /// Assets directory not found.
121    #[error("Assets directory not found: {path}")]
122    AssetsNotFound { path: PathBuf },
123
124    /// Fallback language directory not found.
125    #[error("Fallback language directory not found: {language}")]
126    FallbackLanguageNotFound { language: String },
127
128    /// Invalid language identifier.
129    #[error("Invalid language identifier '{identifier}': {reason}")]
130    InvalidLanguageIdentifier { identifier: String, reason: String },
131
132    /// Language not supported.
133    #[error("Language '{0}' is not supported")]
134    LanguageNotSupported(LanguageIdentifier),
135
136    /// Fluent parsing error.
137    #[error("Fluent parsing error: {0:?}")]
138    FluentParseError(Vec<fluent_syntax::parser::ParserError>),
139
140    /// Fluent serialization error.
141    #[error("Fluent serialization error: {0}")]
142    FluentSerializeError(#[from] std::fmt::Error),
143
144    /// IO error during file operations.
145    #[error("IO error: {0}")]
146    IoError(#[from] std::io::Error),
147
148    /// Environment variable error.
149    #[error("Environment variable error: {0}")]
150    EnvVarError(#[from] std::env::VarError),
151
152    /// Generic backend error.
153    #[error("Backend error: {0}")]
154    BackendError(#[from] anyhow::Error),
155
156    /// JSON serialization error.
157    #[error("JSON error: {0}")]
158    JsonError(#[from] serde_json::Error),
159
160    /// Missing package name.
161    #[error("Missing package name")]
162    MissingPackageName,
163}
164
165impl EsFluentError {
166    /// Creates a configuration not found error.
167    pub fn config_not_found(path: impl Into<PathBuf>) -> Self {
168        Self::ConfigNotFound { path: path.into() }
169    }
170
171    /// Creates an assets not found error.
172    pub fn assets_not_found(path: impl Into<PathBuf>) -> Self {
173        Self::AssetsNotFound { path: path.into() }
174    }
175
176    /// Creates an invalid language identifier error.
177    pub fn invalid_language_identifier(
178        identifier: impl Into<String>,
179        reason: impl Into<String>,
180    ) -> Self {
181        Self::InvalidLanguageIdentifier {
182            identifier: identifier.into(),
183            reason: reason.into(),
184        }
185    }
186
187    /// Creates a fallback language not found error.
188    pub fn fallback_language_not_found(language: impl Into<String>) -> Self {
189        Self::FallbackLanguageNotFound {
190            language: language.into(),
191        }
192    }
193}
194
195/// A result type for common es-fluent operations.
196pub type EsFluentResult<T> = Result<T, EsFluentError>;