Skip to content

Commit f2e4359

Browse files
authored
Return a result for ResourceManager::get_resource (#282)
1 parent 6e1aaa4 commit f2e4359

File tree

4 files changed

+115
-58
lines changed

4 files changed

+115
-58
lines changed

fluent-resmgr/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ unic-langid = "0.9"
2323
elsa = "1.5"
2424
futures = "0.3"
2525
rustc-hash = "1"
26+
thiserror = "1.0"
2627

2728
[dev-dependencies]
2829
unic-langid = { version = "0.9", features = ["macros"]}

fluent-resmgr/examples/simple-resmgr.rs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -40,15 +40,13 @@ fn get_available_locales() -> Result<Vec<LanguageIdentifier>, io::Error> {
4040
.join("examples")
4141
.join("resources");
4242
let res_dir = fs::read_dir(res_path)?;
43-
for entry in res_dir {
44-
if let Ok(entry) = entry {
45-
let path = entry.path();
46-
if path.is_dir() {
47-
if let Some(name) = path.file_name() {
48-
if let Some(name) = name.to_str() {
49-
let langid: LanguageIdentifier = name.parse().expect("Parsing failed.");
50-
locales.push(langid);
51-
}
43+
for entry in res_dir.flatten() {
44+
let path = entry.path();
45+
if path.is_dir() {
46+
if let Some(name) = path.file_name() {
47+
if let Some(name) = name.to_str() {
48+
let langid: LanguageIdentifier = name.parse().expect("Parsing failed.");
49+
locales.push(langid);
5250
}
5351
}
5452
}
@@ -90,10 +88,12 @@ fn main() {
9088
);
9189

9290
// 5. Get a bundle for given paths and locales.
93-
let bundle = mgr.get_bundle(
94-
resolved_locales.into_iter().map(|s| s.to_owned()).collect(),
95-
resources,
96-
);
91+
let bundle = mgr
92+
.get_bundle(
93+
resolved_locales.into_iter().map(|s| s.to_owned()).collect(),
94+
resources,
95+
)
96+
.expect("Could not get bundle");
9797

9898
// 6. Check if the input is provided.
9999
match args.get(1) {

fluent-resmgr/src/resource_manager.rs

Lines changed: 87 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ use fluent_fallback::{
66
};
77
use futures::stream::Stream;
88
use rustc_hash::FxHashSet;
9-
use std::fs;
109
use std::io;
11-
use std::iter;
10+
use std::{fs, iter};
11+
use thiserror::Error;
1212
use unic_langid::LanguageIdentifier;
1313

1414
fn read_file(path: &str) -> Result<String, io::Error> {
@@ -48,21 +48,24 @@ impl ResourceManager {
4848

4949
/// Returns a [`FluentResource`], by either reading the file and loading it into
5050
/// memory, or retrieving it from an in-memory cache.
51-
fn get_resource(&self, resource_id: &str, locale: &str) -> &FluentResource {
51+
fn get_resource(
52+
&self,
53+
res_id: &str,
54+
locale: &str,
55+
) -> Result<&FluentResource, ResourceManagerError> {
5256
let path = self
5357
.path_scheme
5458
.replace("{locale}", locale)
55-
.replace("{res_id}", resource_id);
56-
if let Some(res) = self.resources.get(&path) {
57-
res
59+
.replace("{res_id}", res_id);
60+
Ok(if let Some(resource) = self.resources.get(&path) {
61+
resource
5862
} else {
59-
let string = read_file(&path).unwrap();
60-
let res = match FluentResource::try_new(string) {
61-
Ok(res) => res,
62-
Err((res, _err)) => res,
63+
let resource = match FluentResource::try_new(read_file(&path)?) {
64+
Ok(resource) => resource,
65+
Err((resource, _err)) => resource,
6366
};
64-
self.resources.insert(path.to_string(), Box::new(res))
65-
}
67+
self.resources.insert(path.to_string(), Box::new(resource))
68+
})
6669
}
6770

6871
/// Gets a [`FluentBundle`] from a list of resources. The bundle will only contain the
@@ -74,14 +77,28 @@ impl ResourceManager {
7477
&self,
7578
locales: Vec<LanguageIdentifier>,
7679
resource_ids: Vec<String>,
77-
) -> FluentBundle<&FluentResource> {
80+
) -> Result<FluentBundle<&FluentResource>, Vec<ResourceManagerError>> {
81+
let mut errors: Vec<ResourceManagerError> = vec![];
7882
let mut bundle = FluentBundle::new(locales.clone());
79-
for res_id in &resource_ids {
80-
println!("res_id {:?}", res_id);
81-
let res = self.get_resource(res_id, &locales[0].to_string());
82-
bundle.add_resource(res).unwrap();
83+
let locale = &locales[0];
84+
85+
for resource_id in &resource_ids {
86+
match self.get_resource(resource_id, &locale.to_string()) {
87+
Ok(resource) => {
88+
if let Err(errs) = bundle.add_resource(resource) {
89+
errs.into_iter()
90+
.for_each(|error| errors.push(ResourceManagerError::Fluent(error)))
91+
}
92+
}
93+
Err(error) => errors.push(error),
94+
};
95+
}
96+
97+
if !errors.is_empty() {
98+
Err(errors)
99+
} else {
100+
Ok(bundle)
83101
}
84-
bundle
85102
}
86103

87104
/// Returns an iterator for a [`FluentBundle`] for each locale provided. Each
@@ -92,24 +109,51 @@ impl ResourceManager {
92109
&self,
93110
locales: Vec<LanguageIdentifier>,
94111
resource_ids: Vec<String>,
95-
) -> impl Iterator<Item = FluentBundle<&FluentResource>> {
96-
let res_mgr = self;
112+
) -> impl Iterator<Item = Result<FluentBundle<&FluentResource>, Vec<ResourceManagerError>>>
113+
{
97114
let mut idx = 0;
98115

99116
iter::from_fn(move || {
100117
locales.get(idx).map(|locale| {
101118
idx += 1;
119+
let mut errors: Vec<ResourceManagerError> = vec![];
102120
let mut bundle = FluentBundle::new(vec![locale.clone()]);
121+
103122
for resource_id in &resource_ids {
104-
let resource = res_mgr.get_resource(resource_id, &locale.to_string());
105-
bundle.add_resource(resource).unwrap();
123+
match self.get_resource(resource_id, &locale.to_string()) {
124+
Ok(resource) => {
125+
if let Err(errs) = bundle.add_resource(resource) {
126+
errs.into_iter().for_each(|error| {
127+
errors.push(ResourceManagerError::Fluent(error))
128+
})
129+
}
130+
}
131+
Err(error) => errors.push(error),
132+
}
133+
}
134+
135+
if !errors.is_empty() {
136+
Err(errors)
137+
} else {
138+
Ok(bundle)
106139
}
107-
bundle
108140
})
109141
})
110142
}
111143
}
112144

145+
/// Errors generated during the process of retrieving the localization resources
146+
#[derive(Error, Debug)]
147+
pub enum ResourceManagerError {
148+
/// Error while reading the resource file
149+
#[error("{0}")]
150+
Io(#[from] std::io::Error),
151+
152+
/// Error while trying to add a resource to the bundle
153+
#[error("{0}")]
154+
Fluent(#[from] fluent_bundle::FluentError),
155+
}
156+
113157
// Due to limitation of trait, we need a nameable Iterator type. Due to the
114158
// lack of GATs, these have to own members instead of taking slices.
115159
pub struct BundleIter {
@@ -184,35 +228,37 @@ mod test {
184228
let res_mgr = ResourceManager::new("./tests/resources/{locale}/{res_id}".into());
185229

186230
let _bundle = res_mgr.get_bundle(vec![langid!("en-US")], vec!["test.ftl".into()]);
187-
let res_1 = res_mgr.get_resource("test.ftl", "en-US");
231+
let res_1 = res_mgr
232+
.get_resource("test.ftl", "en-US")
233+
.expect("Could not get resource");
188234

189235
let _bundle2 = res_mgr.get_bundle(vec![langid!("en-US")], vec!["test.ftl".into()]);
190-
let res_2 = res_mgr.get_resource("test.ftl", "en-US");
236+
let res_2 = res_mgr
237+
.get_resource("test.ftl", "en-US")
238+
.expect("Could not get resource");
191239

192240
assert!(
193241
std::ptr::eq(res_1, res_2),
194242
"The resources are cached in memory and reference the same thing."
195243
);
196244
}
197245

198-
// TODO - This API should return a Result instead.
199-
// https://github.com/projectfluent/fluent-rs/issues/278
200246
#[test]
201-
#[should_panic]
202247
fn get_resource_error() {
203248
let res_mgr = ResourceManager::new("./tests/resources/{locale}/{res_id}".into());
204249

205250
let _bundle = res_mgr.get_bundle(vec![langid!("en-US")], vec!["test.ftl".into()]);
206-
res_mgr.get_resource("nonexistent.ftl", "en-US");
251+
let res = res_mgr.get_resource("nonexistent.ftl", "en-US");
252+
253+
assert!(res.is_err());
207254
}
208255

209-
// TODO - This API should return a Result instead.
210-
// https://github.com/projectfluent/fluent-rs/issues/278
211256
#[test]
212-
#[should_panic]
213257
fn get_bundle_error() {
214258
let res_mgr = ResourceManager::new("./tests/resources/{locale}/{res_id}".into());
215-
let _bundle = res_mgr.get_bundle(vec![langid!("en-US")], vec!["nonexistent.ftl".into()]);
259+
let bundle = res_mgr.get_bundle(vec![langid!("en-US")], vec!["nonexistent.ftl".into()]);
260+
261+
assert!(bundle.is_err());
216262
}
217263

218264
// TODO - Syntax errors should be surfaced. This test has an invalid resource that
@@ -221,22 +267,24 @@ mod test {
221267
#[test]
222268
fn get_bundle_ignores_errors() {
223269
let res_mgr = ResourceManager::new("./tests/resources/{locale}/{res_id}".into());
224-
let bundle = res_mgr.get_bundle(
225-
vec![langid!("en-US")],
226-
vec!["test.ftl".into(), "invalid.ftl".into()],
227-
);
270+
let bundle = res_mgr
271+
.get_bundle(
272+
vec![langid!("en-US")],
273+
vec!["test.ftl".into(), "invalid.ftl".into()],
274+
)
275+
.expect("Could not retrieve bundle");
228276

229277
let mut errors = vec![];
230278
let msg = bundle.get_message("hello-world").expect("Message exists");
231279
let pattern = msg.value().expect("Message has a value");
232-
let value = bundle.format_pattern(&pattern, None, &mut errors);
280+
let value = bundle.format_pattern(pattern, None, &mut errors);
233281
assert_eq!(value, "Hello World");
234282
assert!(errors.is_empty());
235283

236284
let mut errors = vec![];
237285
let msg = bundle.get_message("valid-message").expect("Message exists");
238286
let pattern = msg.value().expect("Message has a value");
239-
let value = bundle.format_pattern(&pattern, None, &mut errors);
287+
let value = bundle.format_pattern(pattern, None, &mut errors);
240288
assert_eq!(value, "This is a valid message");
241289
assert!(errors.is_empty());
242290
}

fluent-resmgr/tests/localization_test.rs

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,9 @@ fn localization_format_value() {
3636
fn resmgr_get_bundle() {
3737
let res_mgr = ResourceManager::new("./tests/resources/{locale}/{res_id}".into());
3838

39-
let bundle = res_mgr.get_bundle(vec![langid!("en-US")], vec!["test.ftl".into()]);
39+
let bundle = res_mgr
40+
.get_bundle(vec![langid!("en-US")], vec!["test.ftl".into()])
41+
.expect("Could not get bundle");
4042

4143
let mut errors = vec![];
4244
let msg = bundle.get_message("hello-world").expect("Message exists");
@@ -50,25 +52,31 @@ fn resmgr_get_bundles() {
5052
let res_mgr = ResourceManager::new("./tests/resources/{locale}/{res_id}".into());
5153

5254
let locales = vec![langid!("en-US"), langid!("pl")];
53-
let mut bundles_iter = res_mgr.get_bundles(locales.clone(), vec!["test.ftl".into()]);
55+
let mut bundles_iter = res_mgr.get_bundles(locales, vec!["test.ftl".into()]);
5456

5557
{
56-
let bundle = bundles_iter.next().expect("Failed to get en-US bundle.");
58+
let bundle = bundles_iter
59+
.next()
60+
.unwrap()
61+
.expect("Failed to get en-US bundle.");
5762

5863
let mut errors = vec![];
5964
let msg = bundle.get_message("hello-world").expect("Message exists");
6065
let pattern = msg.value().expect("Message has a value");
61-
let value = bundle.format_pattern(&pattern, None, &mut errors);
66+
let value = bundle.format_pattern(pattern, None, &mut errors);
6267
assert_eq!(value, "Hello World");
6368
}
6469

6570
{
66-
let bundle = bundles_iter.next().expect("Failed to get pl bundle.");
71+
let bundle = bundles_iter
72+
.next()
73+
.unwrap()
74+
.expect("Failed to get pl bundle.");
6775

6876
let mut errors = vec![];
6977
let msg = bundle.get_message("hello-world").expect("Witaj Świecie");
7078
let pattern = msg.value().expect("Message has a value");
71-
let value = bundle.format_pattern(&pattern, None, &mut errors);
79+
let value = bundle.format_pattern(pattern, None, &mut errors);
7280
assert_eq!(value, "Witaj Świecie");
7381
}
7482

0 commit comments

Comments
 (0)