• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

projectfluent / fluent-rs / 8967300005

06 May 2024 09:59AM UTC coverage: 89.957%. Remained the same
8967300005

push

github

web-flow
ci: Fix clippy invocation (#357)

3968 of 4411 relevant lines covered (89.96%)

3444.31 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

76.81
/fluent-bundle/src/types/mod.rs
1
//! `types` module contains types necessary for Fluent runtime
2
//! value handling.
3
//! The core struct is [`FluentValue`] which is a type that can be passed
4
//! to the [`FluentBundle::format_pattern`](crate::bundle::FluentBundle) as an argument, it can be passed
5
//! to any Fluent Function, and any function may return it.
6
//!
7
//! This part of functionality is not fully hashed out yet, since we're waiting
8
//! for the internationalization APIs to mature, at which point all number
9
//! formatting operations will be moved out of Fluent.
10
//!
11
//! For now, [`FluentValue`] can be a string, a number, or a custom [`FluentType`]
12
//! which allows users of the library to implement their own types of values,
13
//! such as dates, or more complex structures needed for their bindings.
14
mod number;
15
mod plural;
16

17
pub use number::*;
18
use plural::PluralRules;
19

20
use std::any::Any;
21
use std::borrow::{Borrow, Cow};
22
use std::fmt;
23
use std::str::FromStr;
24

25
use intl_pluralrules::{PluralCategory, PluralRuleType};
26

27
use crate::memoizer::MemoizerKind;
28
use crate::resolver::Scope;
29
use crate::resource::FluentResource;
30

31
/// Custom types can implement the [`FluentType`] trait in order to generate a string
32
/// value for use in the message generation process.
33
pub trait FluentType: fmt::Debug + AnyEq + 'static {
34
    /// Create a clone of the underlying type.
35
    fn duplicate(&self) -> Box<dyn FluentType + Send>;
36

37
    /// Convert the custom type into a string value, for instance a custom `DateTime`
38
    /// type could return "Oct. 27, 2022".
39
    fn as_string(&self, intls: &intl_memoizer::IntlLangMemoizer) -> Cow<'static, str>;
40

41
    /// Convert the custom type into a string value, for instance a custom `DateTime`
42
    /// type could return "Oct. 27, 2022". This operation is provided the threadsafe
43
    /// [`IntlLangMemoizer`](intl_memoizer::concurrent::IntlLangMemoizer).
44
    fn as_string_threadsafe(
45
        &self,
46
        intls: &intl_memoizer::concurrent::IntlLangMemoizer,
47
    ) -> Cow<'static, str>;
48
}
49

50
impl PartialEq for dyn FluentType + Send {
51
    fn eq(&self, other: &Self) -> bool {
24✔
52
        self.equals(other.as_any())
24✔
53
    }
24✔
54
}
55

56
pub trait AnyEq: Any + 'static {
57
    fn equals(&self, other: &dyn Any) -> bool;
58
    fn as_any(&self) -> &dyn Any;
59
}
60

61
impl<T: Any + PartialEq> AnyEq for T {
62
    fn equals(&self, other: &dyn Any) -> bool {
2✔
63
        other
2✔
64
            .downcast_ref::<Self>()
2✔
65
            .map_or(false, |that| self == that)
2✔
66
    }
2✔
67
    fn as_any(&self) -> &dyn Any {
3✔
68
        self
3✔
69
    }
3✔
70
}
71

72
/// The `FluentValue` enum represents values which can be formatted to a String.
73
///
74
/// Those values are either passed as arguments to [`FluentBundle::format_pattern`] or
75
/// produced by functions, or generated in the process of pattern resolution.
76
///
77
/// [`FluentBundle::format_pattern`]: crate::bundle::FluentBundle::format_pattern
78
#[derive(Debug)]
79
pub enum FluentValue<'source> {
80
    String(Cow<'source, str>),
81
    Number(FluentNumber),
82
    Custom(Box<dyn FluentType + Send>),
83
    None,
84
    Error,
85
}
86

87
impl<'s> PartialEq for FluentValue<'s> {
88
    fn eq(&self, other: &Self) -> bool {
137✔
89
        match (self, other) {
137✔
90
            (FluentValue::String(s), FluentValue::String(s2)) => s == s2,
62✔
91
            (FluentValue::Number(s), FluentValue::Number(s2)) => s == s2,
39✔
92
            (FluentValue::Custom(s), FluentValue::Custom(s2)) => s == s2,
24✔
93
            _ => false,
12✔
94
        }
95
    }
137✔
96
}
97

98
impl<'s> Clone for FluentValue<'s> {
99
    fn clone(&self) -> Self {
168✔
100
        match self {
168✔
101
            FluentValue::String(s) => FluentValue::String(s.clone()),
120✔
102
            FluentValue::Number(s) => FluentValue::Number(s.clone()),
48✔
103
            FluentValue::Custom(s) => {
×
104
                let new_value: Box<dyn FluentType + Send> = s.duplicate();
×
105
                FluentValue::Custom(new_value)
×
106
            }
107
            FluentValue::Error => FluentValue::Error,
×
108
            FluentValue::None => FluentValue::None,
×
109
        }
110
    }
168✔
111
}
112

113
impl<'source> FluentValue<'source> {
114
    /// Attempts to parse the string representation of a `value` that supports
115
    /// [`ToString`] into a [`FluentValue::Number`]. If it fails, it will instead
116
    /// convert it to a [`FluentValue::String`].
117
    ///
118
    /// ```
119
    /// use fluent_bundle::types::{FluentNumber, FluentNumberOptions, FluentValue};
120
    ///
121
    /// // "2" parses into a `FluentNumber`
122
    /// assert_eq!(
123
    ///     FluentValue::try_number("2"),
124
    ///     FluentValue::Number(FluentNumber::new(2.0, FluentNumberOptions::default()))
125
    /// );
126
    ///
127
    /// // Floats can be parsed as well.
128
    /// assert_eq!(
129
    ///     FluentValue::try_number("3.141569"),
130
    ///     FluentValue::Number(FluentNumber::new(
131
    ///         3.141569,
132
    ///         FluentNumberOptions {
133
    ///             minimum_fraction_digits: Some(6),
134
    ///             ..Default::default()
135
    ///         }
136
    ///     ))
137
    /// );
138
    ///
139
    /// // When a value is not a valid number, it falls back to a `FluentValue::String`
140
    /// assert_eq!(
141
    ///     FluentValue::try_number("A string"),
142
    ///     FluentValue::String("A string".into())
143
    /// );
144
    /// ```
145
    pub fn try_number(value: &'source str) -> Self {
711✔
146
        if let Ok(number) = FluentNumber::from_str(value) {
711✔
147
            number.into()
699✔
148
        } else {
149
            value.into()
12✔
150
        }
151
    }
711✔
152

153
    /// Checks to see if two [`FluentValues`](FluentValue) match each other by having the
154
    /// same type and contents. The special exception is in the case of a string being
155
    /// compared to a number. Here attempt to check that the plural rule category matches.
156
    ///
157
    /// ```
158
    /// use fluent_bundle::resolver::Scope;
159
    /// use fluent_bundle::{types::FluentValue, FluentBundle, FluentResource};
160
    /// use unic_langid::langid;
161
    ///
162
    /// let langid_ars = langid!("en");
163
    /// let bundle: FluentBundle<FluentResource> = FluentBundle::new(vec![langid_ars]);
164
    /// let scope = Scope::new(&bundle, None, None);
165
    ///
166
    /// // Matching examples:
167
    /// assert!(FluentValue::try_number("2").matches(&FluentValue::try_number("2"), &scope));
168
    /// assert!(FluentValue::from("fluent").matches(&FluentValue::from("fluent"), &scope));
169
    /// assert!(
170
    ///     FluentValue::from("one").matches(&FluentValue::try_number("1"), &scope),
171
    ///     "Plural rules are matched."
172
    /// );
173
    ///
174
    /// // Non-matching examples:
175
    /// assert!(!FluentValue::try_number("2").matches(&FluentValue::try_number("3"), &scope));
176
    /// assert!(!FluentValue::from("fluent").matches(&FluentValue::from("not fluent"), &scope));
177
    /// assert!(!FluentValue::from("two").matches(&FluentValue::try_number("100"), &scope),);
178
    /// ```
179
    pub fn matches<R: Borrow<FluentResource>, M>(
95✔
180
        &self,
95✔
181
        other: &FluentValue,
95✔
182
        scope: &Scope<R, M>,
95✔
183
    ) -> bool
95✔
184
    where
95✔
185
        M: MemoizerKind,
95✔
186
    {
95✔
187
        match (self, other) {
95✔
188
            (FluentValue::String(a), FluentValue::String(b)) => a == b,
58✔
189
            (FluentValue::Number(a), FluentValue::Number(b)) => a == b,
14✔
190
            (FluentValue::String(a), FluentValue::Number(b)) => {
22✔
191
                let cat = match a.as_ref() {
22✔
192
                    "zero" => PluralCategory::ZERO,
22✔
193
                    "one" => PluralCategory::ONE,
21✔
194
                    "two" => PluralCategory::TWO,
14✔
195
                    "few" => PluralCategory::FEW,
13✔
196
                    "many" => PluralCategory::MANY,
12✔
197
                    "other" => PluralCategory::OTHER,
11✔
198
                    _ => return false,
9✔
199
                };
200
                // This string matches a plural rule keyword. Check if the number
201
                // matches the plural rule category.
202
                scope
13✔
203
                    .bundle
13✔
204
                    .intls
13✔
205
                    .with_try_get_threadsafe::<PluralRules, _, _>(
13✔
206
                        (PluralRuleType::CARDINAL,),
13✔
207
                        |pr| pr.0.select(b) == Ok(cat),
13✔
208
                    )
13✔
209
                    .unwrap()
13✔
210
            }
211
            _ => false,
1✔
212
        }
213
    }
95✔
214

215
    /// Write out a string version of the [`FluentValue`] to `W`.
216
    pub fn write<W, R, M>(&self, w: &mut W, scope: &Scope<R, M>) -> fmt::Result
38✔
217
    where
38✔
218
        W: fmt::Write,
38✔
219
        R: Borrow<FluentResource>,
38✔
220
        M: MemoizerKind,
38✔
221
    {
38✔
222
        if let Some(formatter) = &scope.bundle.formatter {
38✔
223
            if let Some(val) = formatter(self, &scope.bundle.intls) {
1✔
224
                return w.write_str(&val);
1✔
225
            }
×
226
        }
37✔
227
        match self {
37✔
228
            FluentValue::String(s) => w.write_str(s),
11✔
229
            FluentValue::Number(n) => w.write_str(&n.as_string()),
26✔
230
            FluentValue::Custom(s) => w.write_str(&scope.bundle.intls.stringify_value(&**s)),
×
231
            FluentValue::Error => Ok(()),
×
232
            FluentValue::None => Ok(()),
×
233
        }
234
    }
38✔
235

236
    /// Converts the [`FluentValue`] to a string.
237
    ///
238
    /// Clones inner values when owned, borrowed data is not cloned.
239
    /// Prefer using [`FluentValue::into_string()`] when possible.
240
    pub fn as_string<R: Borrow<FluentResource>, M>(&self, scope: &Scope<R, M>) -> Cow<'source, str>
×
241
    where
×
242
        M: MemoizerKind,
×
243
    {
×
244
        if let Some(formatter) = &scope.bundle.formatter {
×
245
            if let Some(val) = formatter(self, &scope.bundle.intls) {
×
246
                return val.into();
×
247
            }
×
248
        }
×
249
        match self {
×
250
            FluentValue::String(s) => s.clone(),
×
251
            FluentValue::Number(n) => n.as_string(),
×
252
            FluentValue::Custom(s) => scope.bundle.intls.stringify_value(&**s),
×
253
            FluentValue::Error => "".into(),
×
254
            FluentValue::None => "".into(),
×
255
        }
256
    }
×
257

258
    /// Converts the [`FluentValue`] to a string.
259
    ///
260
    /// Takes self by-value to be able to skip expensive clones.
261
    /// Prefer this method over [`FluentValue::as_string()`] when possible.
262
    pub fn into_string<R: Borrow<FluentResource>, M>(self, scope: &Scope<R, M>) -> Cow<'source, str>
202✔
263
    where
202✔
264
        M: MemoizerKind,
202✔
265
    {
202✔
266
        if let Some(formatter) = &scope.bundle.formatter {
202✔
267
            if let Some(val) = formatter(&self, &scope.bundle.intls) {
1✔
268
                return val.into();
×
269
            }
1✔
270
        }
201✔
271
        match self {
202✔
272
            FluentValue::String(s) => s,
195✔
273
            FluentValue::Number(n) => n.as_string(),
5✔
274
            FluentValue::Custom(s) => scope.bundle.intls.stringify_value(s.as_ref()),
2✔
275
            FluentValue::Error => "".into(),
×
276
            FluentValue::None => "".into(),
×
277
        }
278
    }
202✔
279

280
    pub fn into_owned<'a>(&self) -> FluentValue<'a> {
252✔
281
        match self {
252✔
282
            FluentValue::String(str) => FluentValue::String(Cow::from(str.to_string())),
120✔
283
            FluentValue::Number(s) => FluentValue::Number(s.clone()),
108✔
284
            FluentValue::Custom(s) => FluentValue::Custom(s.duplicate()),
12✔
285
            FluentValue::Error => FluentValue::Error,
×
286
            FluentValue::None => FluentValue::None,
12✔
287
        }
288
    }
252✔
289
}
290

291
impl<'source> From<String> for FluentValue<'source> {
292
    fn from(s: String) -> Self {
2,112✔
293
        FluentValue::String(s.into())
2,112✔
294
    }
2,112✔
295
}
296

297
impl<'source> From<&'source String> for FluentValue<'source> {
298
    fn from(s: &'source String) -> Self {
×
299
        FluentValue::String(s.into())
×
300
    }
×
301
}
302

303
impl<'source> From<&'source str> for FluentValue<'source> {
304
    fn from(s: &'source str) -> Self {
1,682✔
305
        FluentValue::String(s.into())
1,682✔
306
    }
1,682✔
307
}
308

309
impl<'source> From<Cow<'source, str>> for FluentValue<'source> {
310
    fn from(s: Cow<'source, str>) -> Self {
252✔
311
        FluentValue::String(s)
252✔
312
    }
252✔
313
}
314

315
impl<'source, T> From<Option<T>> for FluentValue<'source>
316
where
317
    T: Into<FluentValue<'source>>,
318
{
319
    fn from(v: Option<T>) -> Self {
3✔
320
        match v {
3✔
321
            Some(v) => v.into(),
2✔
322
            None => FluentValue::None,
1✔
323
        }
324
    }
3✔
325
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc