#[derive(Debug)]
pub(crate) enum LocalRead<T> {
Answered(T),
Empty(T),
Unavailable,
}
impl<T> LocalRead<T> {
pub(crate) fn classify<E>(read: Result<T, E>, has_items: impl FnOnce(&T) -> bool) -> Self {
match read {
Ok(listing) if has_items(&listing) => Self::Answered(listing),
Ok(listing) => Self::Empty(listing),
Err(_) => Self::Unavailable,
}
}
pub(crate) fn final_answer(self, remote_configured: bool) -> Option<T> {
match self {
Self::Answered(listing) => Some(listing),
Self::Empty(listing) if !remote_configured => Some(listing),
Self::Empty(_) | Self::Unavailable => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[derive(Debug, PartialEq, Eq)]
struct Listing(Vec<&'static str>);
fn classify(read: Result<Listing, ()>) -> LocalRead<Listing> {
LocalRead::classify(read, |listing| !listing.0.is_empty())
}
#[test]
fn a_local_store_with_items_answers_even_when_a_remote_is_configured() {
let local = classify(Ok(Listing(vec!["ada"])));
assert_eq!(local.final_answer(true), Some(Listing(vec!["ada"])));
}
#[test]
fn an_empty_local_store_falls_through_to_a_configured_remote() {
let local = classify(Ok(Listing(vec![])));
assert_eq!(
local.final_answer(true),
None,
"an empty local store must not short-circuit a configured remote — \
that is how a user's real contacts and events went missing"
);
}
#[test]
fn an_empty_local_store_is_the_answer_when_no_remote_is_configured() {
let local = classify(Ok(Listing(vec![])));
assert_eq!(local.final_answer(false), Some(Listing(vec![])));
}
#[test]
fn an_unreadable_local_store_never_answers() {
assert_eq!(classify(Err(())).final_answer(true), None);
assert_eq!(
classify(Err(())).final_answer(false),
None,
"with no remote and no local read the caller's pending tail is \
correct — there is no listing to return"
);
}
}