1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
// This file is part of ICU4X. For terms of use, please see the file
// called LICENSE at the top level of the ICU4X source tree
// (online at: https://github.com/unicode-org/icu4x/blob/main/LICENSE ).

//! Collection of predicate traits and functions for forking providers.

use icu_provider::prelude::*;

/// The predicate trait used by [`ForkByErrorProvider`].
///
/// [`ForkByErrorProvider`]: super::ForkByErrorProvider
pub trait ForkByErrorPredicate {
    /// The error to return if there are zero providers.
    const UNIT_ERROR: DataErrorKind = DataErrorKind::MissingDataKey;

    /// This function is called when a data request fails and there are additional providers
    /// that could possibly fulfill the request.
    ///
    /// Arguments:
    ///
    /// - `&self` = Reference to the struct implementing the trait (for data capture)
    /// - `key` = The [`DataKey`] associated with the request
    /// - `req` = The [`DataRequest`]. This may be `None` if there is no request, such as
    ///           inside [`IterableDynamicDataProvider`].
    /// - `err` = The error that occurred.
    ///
    /// Return value:
    ///
    /// - `true` to discard the error and attempt the request with the next provider.
    /// - `false` to return the error and not perform any additional requests.
    ///
    /// [`DataKey`]: icu_provider::DataKey
    /// [`DataRequest`]: icu_provider::DataRequest
    /// [`IterableDynamicDataProvider`]: icu_provider::datagen::IterableDynamicDataProvider
    fn test(&self, key: DataKey, req: Option<DataRequest>, err: DataError) -> bool;
}

/// A predicate that allows forking providers to search for a provider that supports a
/// particular data key.
///
/// This is normally used implicitly by [`ForkByKeyProvider`].
///
/// [`ForkByKeyProvider`]: super::ForkByKeyProvider
#[derive(Debug, PartialEq, Eq)]
#[non_exhaustive] // Not intended to be constructed
pub struct MissingDataKeyPredicate;

impl ForkByErrorPredicate for MissingDataKeyPredicate {
    const UNIT_ERROR: DataErrorKind = DataErrorKind::MissingDataKey;

    #[inline]
    fn test(&self, _: DataKey, _: Option<DataRequest>, err: DataError) -> bool {
        matches!(
            err,
            DataError {
                kind: DataErrorKind::MissingDataKey,
                ..
            }
        )
    }
}

/// A predicate that allows forking providers to search for a provider that supports a
/// particular locale, based on whether it returns [`DataErrorKind::MissingLocale`].
///
/// # Examples
///
/// Configure a multi-language data provider pointing at two language packs:
///
/// ```
/// use icu_provider_adapters::fork::ForkByErrorProvider;
/// use icu_provider_adapters::fork::predicates::MissingLocalePredicate;
/// use icu_provider_fs::FsDataProvider;
/// use icu_provider::prelude::*;
/// use icu_provider::hello_world::HelloWorldV1Marker;
/// use icu_locid::langid;
///
/// // The `tests` directory contains two separate "language packs" for Hello World data.
/// let provider_de = FsDataProvider::try_new("tests/data/langtest/de").unwrap();
/// let provider_ro = FsDataProvider::try_new("tests/data/langtest/ro").unwrap();
///
/// // Create the forking provider:
/// let provider = ForkByErrorProvider::new_with_predicate(
///     provider_de,
///     provider_ro,
///     MissingLocalePredicate
/// );
///
/// // Test that we can load both "de" and "ro" data:
///
/// let german_hello_world: DataPayload<HelloWorldV1Marker> = provider
///     .as_deserializing()
///     .load(DataRequest {
///         locale: &langid!("de").into(),
///         metadata: Default::default(),
///     })
///     .expect("Loading should succeed")
///     .take_payload()
///     .expect("Data should be present");
///
/// assert_eq!("Hallo Welt", german_hello_world.get().message);
///
/// let romanian_hello_world: DataPayload<HelloWorldV1Marker> = provider
///     .as_deserializing()
///     .load(DataRequest {
///         locale: &langid!("ro").into(),
///         metadata: Default::default(),
///     })
///     .expect("Loading should succeed")
///     .take_payload()
///     .expect("Data should be present");
///
/// assert_eq!("Salut, lume", romanian_hello_world.get().message);
///
/// // We should not be able to load "en" data because it is not in the provider:
///
/// DataProvider::<HelloWorldV1Marker>::load(
///     &provider.as_deserializing(),
///     DataRequest {
///         locale: &langid!("en").into(),
///         metadata: Default::default(),
///     }
/// )
/// .expect_err("No English data");
/// ```
#[derive(Debug, PartialEq, Eq)]
#[allow(clippy::exhaustive_structs)] // empty type
pub struct MissingLocalePredicate;

impl ForkByErrorPredicate for MissingLocalePredicate {
    const UNIT_ERROR: DataErrorKind = DataErrorKind::MissingLocale;

    #[inline]
    fn test(&self, _: DataKey, _: Option<DataRequest>, err: DataError) -> bool {
        matches!(
            err,
            DataError {
                kind: DataErrorKind::MissingLocale,
                ..
            }
        )
    }
}