Skip to main content

fluent_typed/
error.rs

1//! The runtime error type, [`L10nError`].
2
3use std::fmt;
4
5/// An error from loading a localization bundle or formatting one of its
6/// messages at runtime.
7///
8/// Returned by [`L10nBundle`](crate::prelude::L10nBundle),
9/// [`L10nLanguageVec`](crate::prelude::L10nLanguageVec) and the generated
10/// `L10nLanguage::new`. The generated message accessors `unwrap` this: a failure
11/// there means the generated code and the embedded `.ftl` have drifted apart,
12/// which is a build-time bug rather than something to handle at runtime.
13///
14/// The variants carry owned strings rather than `fluent-bundle`'s own error
15/// types on purpose, so this enum does not change shape when that (pre-1.0)
16/// dependency is updated. It is `#[non_exhaustive]` so new variants can be added
17/// in a minor release.
18#[derive(Debug)]
19#[non_exhaustive]
20pub enum L10nError {
21    /// The user-supplied decompressor (for a compressed single-file build)
22    /// returned an error. Carries that error's message.
23    Decompression(String),
24
25    /// The `.ftl` bytes were not valid UTF-8.
26    InvalidUtf8(std::string::FromUtf8Error),
27
28    /// The language identifier (e.g. `"en-US"`) could not be parsed.
29    InvalidLanguage {
30        /// The identifier that failed to parse.
31        lang: String,
32        /// A human-readable description of why it failed.
33        reason: String,
34    },
35
36    /// The Fluent builtins (`NUMBER`, …) could not be registered on the bundle.
37    Builtins(String),
38
39    /// The `.ftl` resource failed to parse. One entry per parser error.
40    ResourceParse(Vec<String>),
41
42    /// The resource could not be added to the bundle (e.g. a message id that
43    /// collides with one already present).
44    AddResource(Vec<String>),
45
46    /// No message with this id exists in the bundle.
47    MessageNotFound {
48        /// The message id that was looked up.
49        id: String,
50    },
51
52    /// The message exists but has no value of its own (it has only attributes).
53    MessageHasNoValue {
54        /// The message id that was looked up.
55        id: String,
56    },
57
58    /// The message exists but has no attribute with this name.
59    AttributeNotFound {
60        /// The message id.
61        id: String,
62        /// The attribute that was looked up.
63        attribute: String,
64    },
65
66    /// Formatting the message or attribute failed — typically a missing or
67    /// wrongly-typed argument. One entry per formatting error.
68    Format {
69        /// The message id being formatted.
70        id: String,
71        /// The attribute being formatted, if any.
72        attribute: Option<String>,
73        /// One entry per formatting error reported by fluent-bundle.
74        errors: Vec<String>,
75    },
76
77    /// External `.ftl` content failed validation against the message contract
78    /// compiled into the generated API (see [`validate_ftl`](crate::validate_ftl)
79    /// and the generated `L10nLanguage::new_external`). Every violation is
80    /// listed, not just the first.
81    Validation {
82        /// One entry per contract violation.
83        violations: Vec<crate::ContractViolation>,
84    },
85}
86
87impl fmt::Display for L10nError {
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        match self {
90            Self::Decompression(e) => write!(f, "Could not decompress the .ftl data: {e}"),
91            Self::InvalidUtf8(e) => write!(f, "The .ftl content is not valid UTF-8: {e}"),
92            Self::InvalidLanguage { lang, reason } => {
93                write!(f, "Invalid language identifier '{lang}': {reason}")
94            }
95            Self::Builtins(e) => write!(f, "Could not register the Fluent builtins: {e}"),
96            Self::ResourceParse(errors) => {
97                write!(f, "Could not parse the .ftl resource:")?;
98                for e in errors {
99                    write!(f, "\n  {e}")?;
100                }
101                Ok(())
102            }
103            Self::AddResource(errors) => {
104                write!(f, "Could not add the .ftl resource to the bundle:")?;
105                for e in errors {
106                    write!(f, "\n  {e}")?;
107                }
108                Ok(())
109            }
110            Self::MessageNotFound { id } => write!(f, "No message '{id}' found"),
111            Self::MessageHasNoValue { id } => {
112                write!(f, "Message '{id}' has no value of its own")
113            }
114            Self::AttributeNotFound { id, attribute } => {
115                write!(f, "Message '{id}' has no attribute '{attribute}'")
116            }
117            Self::Format {
118                id,
119                attribute,
120                errors,
121            } => {
122                match attribute {
123                    Some(a) => write!(f, "Could not format attribute '{a}' of message '{id}':")?,
124                    None => write!(f, "Could not format message '{id}':")?,
125                }
126                for e in errors {
127                    write!(f, "\n  {e}")?;
128                }
129                Ok(())
130            }
131            Self::Validation { violations } => {
132                write!(
133                    f,
134                    "The .ftl content does not satisfy the compiled message contract:"
135                )?;
136                for v in violations {
137                    write!(f, "\n  {v}")?;
138                }
139                Ok(())
140            }
141        }
142    }
143}
144
145impl std::error::Error for L10nError {
146    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
147        match self {
148            Self::InvalidUtf8(e) => Some(e),
149            _ => None,
150        }
151    }
152}