Reputation: 6110
I have a nested object in mongodb that I want to deserialize/decode back into a struct. Here's the structure :
pub struct ItemUnit {
/// unit's name like PCS, DOZEN, PACK, etc
pub name: String,
/// denote this unit value
pub multiplier: f64,
/// item's price in this unit
pub price: Option<f64>,
}
pub struct Item {
/// item's name
pub name: String,
/// available unit types available for this item, in an array format
pub units: Vec<ItemUnit>,
}
as you can see the struct is nested and units
is an array (Vec<ItemUnit>
).
As Rust have a culture of "not letting you go with error possibility", this quickly becomes tricky. In Rust mongodb driver, you have to deserialize/decode them back from 'bson' document into "native type" such as String, f64, and so on. Each deserialize operation returns Result<>
since there's a possibility of error in it.
I have a problem with nested object with an array of another object in it:
// doc is the data from a single `Item`
// since `get_array` might throw a ValueAccess error, I map it into my own custom Error type
// so `units_bson` is now a `Vec<mongodb::bson::Bson>` type
let units_bson = doc.get_array("units").map_err(Self::map_mongodb_err)?;
// in the next step, I need to decode the mongodb::bson::Bson for each element in this array
/// here's I use map() with closure
let units: Vec<ItemUnit> = units_bson.iter().map(|u| {
// get_str() returns Result<T,E> but since ItemUnit only accepts a string not Result<> type
// I had to handle the error possibilities here in the closure/map function
// but the ? operator only available on a function that returns `Result` or `Option`
// (or another type that implements `Try`)
let name = u.as_document().unwrap().get_str("name").map_err(Self::map_mongodb_err);
return ItemUnit { name, multiplier, p_buy };
}).collect();
So my question is how do you catch an error inside a closure?
Or is there any other workaround like try-catch block that can also catch any error inside a closure ?
Upvotes: 2
Views: 980
Reputation: 70337
The first thing you need to do is propagate the error into the Vec
.
units_bson.iter().map(|u| {
// This question-mark returns from the closure, not the whole function.
let name = code_and_stuff.map_err(...)?;
// We're returning Result values now, so wrap the value in Ok.
Ok(ItemUnit { name, ... })
});
At this point, we have an iterator over values of type Result<ItemUnit, SomeError>
. If we were to call collect
now, naively we might expect to get a Vec<Result<ItemUnit, SomeError>>
, and indeed that is one correct result. But we can also request a Result<Vec<ItemUnit>, SomeError>
, because Result<A, E>
has a FromIterator
instance for this exact use case.
units_bson.iter().map(|u| {
// This question-mark returns from the closure, not the whole function.
let name = code_and_stuff.map_err(...)?;
// We're returning Result values now, so wrap the value in Ok.
Ok(ItemUnit { name, ... })
}).collect::<Result<Vec<_>, _>>()?;
Then, with one more question mark after our collect
call, we propagate the error to our outer function's result.
Welcome to the wonderful world of Rust error values. I promise this sort of thing becomes second nature the more you do it; you won't even notice yourself adding the little question marks in a handful of places.
Upvotes: 2