common: add iterhelpers

Summary:
This just adds a single fn. I did not come up with a better place/name to put
it, suggestions are welcome. Seems generic enough to belong at the top-level
common location.

I've already needed this twice, so decided to extract. Second callsite will be further in the stack.

Reviewed By: StanislavGlebik

Differential Revision: D24080193

fbshipit-source-id: c3e0646f263562f3eed93f1fdbab9a076729f33c
This commit is contained in:
Kostia Balytskyi 2020-10-04 23:49:34 -07:00 committed by Facebook GitHub Bot
parent 2ea25308ab
commit 081ca3e7d6
5 changed files with 92 additions and 22 deletions

View File

@ -252,6 +252,7 @@ members = [
"common/async_limiter/examples/tokio_v2",
"common/bounded_traversal",
"common/dedupmap",
"common/iterhelpers",
"common/rust/caching_ext",
"common/rust/slog_ext",
"common/rust/sql_ext",

View File

@ -16,6 +16,7 @@ path = "test/main.rs"
[dependencies]
commitsync = { path = "../../../../configerator/structs/scm/mononoke/repos/commitsync" }
context = { path = "../../server/context" }
iterhelpers = { path = "../../common/iterhelpers" }
metaconfig_parser = { path = "../../metaconfig/parser" }
metaconfig_types = { path = "../../metaconfig/types" }
mononoke_types = { path = "../../mononoke_types" }

View File

@ -11,6 +11,7 @@ use anyhow::{anyhow, Error, Result};
use cached_config::{ConfigHandle, ConfigStore};
use commitsync::types::{RawCommitSyncAllVersions, RawCommitSyncCurrentVersions};
use context::CoreContext;
use iterhelpers::get_only_item;
use metaconfig_parser::Convert;
use metaconfig_types::{CommitSyncConfig, CommitSyncConfigVersion};
use mononoke_types::RepositoryId;
@ -158,23 +159,6 @@ impl CfgrLiveCommitSyncConfig {
.iter()
.any(|small_repo| small_repo.repoid == repo_id.id())
}
/// Return a clone of the only item in an iterator
/// Error out otherwise
fn get_only_item<T: Clone, I: IntoIterator<Item = T>, N: Fn() -> Error, M: Fn() -> Error>(
items: I,
no_items_error: N,
many_items_error: M,
) -> Result<T> {
let mut iter = items.into_iter();
let maybe_first = iter.next();
let maybe_second = iter.next();
match (maybe_first, maybe_second) {
(None, None) => Err(no_items_error()),
(Some(only_item), None) => Ok(only_item.clone()),
(_, _) => return Err(many_items_error()),
}
}
}
impl LiveCommitSyncConfig for CfgrLiveCommitSyncConfig {
@ -222,12 +206,12 @@ impl LiveCommitSyncConfig for CfgrLiveCommitSyncConfig {
})
.map(|(_, commit_sync_config)| commit_sync_config);
Self::get_only_item(
let res: Result<_, Error> = get_only_item(
interesting_top_level_configs,
|| ErrorKind::NotPartOfAnyCommitSyncConfig(repo_id).into(),
|| ErrorKind::PartOfMultipleCommitSyncConfigs(repo_id).into(),
)?
.clone()
|| ErrorKind::NotPartOfAnyCommitSyncConfig(repo_id),
|_, _| ErrorKind::PartOfMultipleCommitSyncConfigs(repo_id),
);
res?.clone()
};
let commit_sync_config = raw_commit_sync_config.convert()?;

View File

@ -0,0 +1,10 @@
[package]
name = "iterhelpers"
edition = "2018"
version = "0.1.0"
authors = ['Facebook']
license = "GPLv2+"
include = ["src/*.rs"]
[dev-dependencies]
anyhow = "1.0"

View File

@ -0,0 +1,74 @@
/*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This software may be used and distributed according to the terms of the
* GNU General Public License version 2.
*/
/// In case iterator has 0 or 2+ items, fails with errors
/// produced by corresponding error factories
pub fn get_only_item<T, E, N, NE, M, ME>(
items: impl IntoIterator<Item = T>,
no_items_error: N,
many_items_error: M,
) -> Result<T, E>
where
N: FnOnce() -> NE,
NE: Into<E>,
M: FnOnce(T, T) -> ME,
ME: Into<E>,
{
let mut iter = items.into_iter();
let maybe_first = iter.next();
let maybe_second = iter.next();
match (maybe_first, maybe_second) {
(None, None) => Err(no_items_error().into()),
(Some(only_item), None) => Ok(only_item),
(Some(item1), Some(item2)) => Err(many_items_error(item1, item2).into()),
(None, Some(_)) => panic!("iterator returns Some after None"),
}
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::{anyhow, Error};
#[test]
fn test_success() {
let v: Vec<u8> = vec![1];
let r: Result<_, Error> =
get_only_item(v, || anyhow!("no items"), |_, _| anyhow!("many items"));
assert_eq!(r.unwrap(), 1);
}
#[test]
fn test_no_items() {
let v: Vec<u8> = vec![];
let res: Result<_, Error> =
get_only_item(v, || anyhow!("no items"), |_, _| anyhow!("many items"));
assert!(res.is_err());
assert_eq!(format!("{}", res.unwrap_err()), "no items".to_string());
}
#[test]
fn test_too_many_items() {
let v: Vec<u8> = vec![1, 2, 3];
let res: Result<_, Error> =
get_only_item(v, || anyhow!("no items"), |_, _| anyhow!("many items"));
assert!(res.is_err());
assert_eq!(format!("{}", res.unwrap_err()), "many items".to_string());
}
#[test]
fn test_too_many_items_args() {
let v: Vec<u8> = vec![1, 2, 3];
let too_many_items = |i1, i2| {
assert_eq!(i1, 1);
assert_eq!(i2, 2);
anyhow!("many items")
};
let res: Result<_, Error> = get_only_item(v, || anyhow!("no items"), too_many_items);
assert!(res.is_err());
}
}