cli: Simplify private repo fetching

* Remove `rad sync --force` flag
* Remove `rad clone --private` flag

These flags are no longer necessary. Simplify specifying which seed
to fetch from with `--seed` is enough.
This commit is contained in:
cloudhead 2024-05-15 21:54:51 +02:00
parent ca7db1620c
commit 8402b85ad9
No known key found for this signature in database
12 changed files with 55 additions and 101 deletions

View File

@ -4,10 +4,10 @@ automatically connect to the necessary seeds.
``` ```
$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji $ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all' ✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all'
✓ Connecting to z6Mkt67…v4N1tRk@[..]
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk..
✓ Connecting to z6MknSL…StBU8Vi@[..] ✓ Connecting to z6MknSL…StBU8Vi@[..]
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi.. ✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Connecting to z6Mkt67…v4N1tRk@[..]
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk..
✓ Creating checkout in ./heartwood.. ✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added ✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi ✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi

View File

@ -16,8 +16,8 @@ still returns successfully.
``` ```
$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --timeout 3 $ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --timeout 3
✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all' ✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all'
✗ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk.. error: failed to perform fetch handshake
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi.. ✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✗ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkt67…v4N1tRk.. error: failed to perform fetch handshake
✗ Connecting to z6MksFq…bS9wzpT@[..].. error: connection reset ✗ Connecting to z6MksFq…bS9wzpT@[..].. error: connection reset
✓ Creating checkout in ./heartwood.. ✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added ✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added

View File

@ -169,8 +169,8 @@ sync` and fetch his references:
``` ~bob ``` ~bob
$ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji $ rad clone rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji
✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all' ✓ Seeding policy updated for rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji with scope 'all'
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z.. ✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6Mkux1…nVhib7Z..
✓ Fetching rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji from z6MknSL…StBU8Vi..
✓ Creating checkout in ./heartwood.. ✓ Creating checkout in ./heartwood..
✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added ✓ Remote alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi added
✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi ✓ Remote-tracking branch alice@z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi/master created for z6MknSL…StBU8Vi

View File

@ -29,7 +29,7 @@ $ rad inspect --identity
``` ~bob ``` ~bob
$ rad ls --all --private $ rad ls --all --private
$ rad clone rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --private --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1 $ rad clone rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1
✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'all' ✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'all'
✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi.. ✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi..
✓ Creating checkout in ./heartwood.. ✓ Creating checkout in ./heartwood..

View File

@ -5,7 +5,7 @@ Bob tries to fetch it, and even though he's connected to Alice, it fails.
$ rad ls $ rad ls
``` ```
``` ~bob (fail) ``` ~bob (fail)
$ rad clone rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --private --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1 $ rad clone rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --timeout 1
✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'all' ✓ Seeding policy updated for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu with scope 'all'
✗ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi.. error: failed to perform fetch handshake ✗ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi.. error: failed to perform fetch handshake
✗ Error: repository rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu not found ✗ Error: repository rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu not found

View File

@ -20,19 +20,10 @@ $ rad sync rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --fetch
✗ Error: no seeds found for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu ✗ Error: no seeds found for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu
``` ```
If he specifies a seed that isn't in his routing table, he gets a warning and He has to specify a seed that isn't in his routing table:
an error:
``` ~bob ``` ~bob
$ rad sync rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --fetch --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi $ rad sync rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --fetch --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi
! Warning: node z6MknSL…StBU8Vi is not connected or seeding.. skipping
✗ Error: no seeds found for rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu
```
For this to work, Bob has to force the sync:
``` ~bob
$ rad sync rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --fetch --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --force
✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi.. ✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi..
✓ Fetched repository from 1 seed(s) ✓ Fetched repository from 1 seed(s)
``` ```
@ -50,8 +41,8 @@ Note that if multiple seeds are specified, the command succeeds as long as one
seed succeeds. seed succeeds.
``` ~bob ``` ~bob
$ rad sync rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --fetch --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --seed z6MkwPUeUS2fJMfc2HZN1RQTQcTTuhw4HhPySB8JeUg2mVvx --force $ rad sync rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu --fetch --seed z6MknSLrJoTcukLrE435hVNQT4JUhbvWLX4kUzqkEStBU8Vi --seed z6MkwPUeUS2fJMfc2HZN1RQTQcTTuhw4HhPySB8JeUg2mVvx
✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi.. ✓ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MknSL…StBU8Vi..
✗ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MkwPU…Ug2mVvx.. error: session does not exist; cannot initiate fetch ✗ Fetching rad:z2ug5mwNKZB8KGpBDRTrWHAMbvHCu from z6MkwPU…Ug2mVvx.. error: peer is not connected; cannot initiate fetch
✓ Fetched repository from 1 seed(s) ✓ Fetched repository from 1 seed(s)
``` ```

View File

@ -110,6 +110,13 @@ $ rad sync rad:z39mP9rQAaGmERfUMPULfPUi473tY
✗ Error: nothing to announce, repository rad:z39mP9rQAaGmERfUMPULfPUi473tY is not available locally ✗ Error: nothing to announce, repository rad:z39mP9rQAaGmERfUMPULfPUi473tY is not available locally
``` ```
Or when trying to fetch from a seed you aren't connected to, using `--seed`:
```
$ rad sync --fetch rad:z39mP9rQAaGmERfUMPULfPUi473tY --seed z6MkjM3HpqNVV4ZsL5s3RAd8ThVG3VG98YsDCjHBNnGMq5o7
✗ Fetching rad:z39mP9rQAaGmERfUMPULfPUi473tY from z6MkjM3…nGMq5o7.. error: peer is not connected; cannot initiate fetch
✗ Error: repository fetch from 1 seed(s) failed
```
Also note that you cannot sync an unseeded repo: Also note that you cannot sync an unseeded repo:
``` ```
$ rad unseed rad:z39mP9rQAaGmERfUMPULfPUi473tY $ rad unseed rad:z39mP9rQAaGmERfUMPULfPUi473tY

View File

@ -41,14 +41,13 @@ Usage
The `clone` command will use your local node's routing table to find seeds from The `clone` command will use your local node's routing table to find seeds from
which it can clone the repository. which it can clone the repository.
For private repositories, use the `--private` and `--seed` options, to clone directly For private repositories, use the `--seed` options, to clone directly
from known seeds in the privacy set. from known seeds in the privacy set.
Options Options
--scope <scope> Follow scope: `followed` or `all` (default: all) --scope <scope> Follow scope: `followed` or `all` (default: all)
-s, --seed <nid> Clone from this seed (may be specified multiple times) -s, --seed <nid> Clone from this seed (may be specified multiple times)
--private Clone a private repository
--timeout <secs> Timeout for fetching repository (default: 9) --timeout <secs> Timeout for fetching repository (default: 9)
--help Print help --help Print help
@ -91,9 +90,6 @@ impl Args for Options {
scope = term::args::parse_value("scope", value)?; scope = term::args::parse_value("scope", value)?;
} }
Long("private") => {
sync.force = true;
}
Long("timeout") => { Long("timeout") => {
let value = parser.value()?; let value = parser.value()?;
let secs = term::args::number(&value)?; let secs = term::args::number(&value)?;

View File

@ -43,9 +43,6 @@ Usage
When `--fetch` is specified, any number of seeds may be given When `--fetch` is specified, any number of seeds may be given
using the `--seed` option, eg. `--seed <nid>@<addr>:<port>`. using the `--seed` option, eg. `--seed <nid>@<addr>:<port>`.
To force a fetch even if there is no route to a seed (as is the case for
private repositories), `--force` can be used.
When `--replicas` is specified, the given replication factor will try When `--replicas` is specified, the given replication factor will try
to be matched. For example, `--replicas 5` will sync with 5 seeds. to be matched. For example, `--replicas 5` will sync with 5 seeds.
@ -65,7 +62,6 @@ Options
-f, --fetch Turn on fetching (default: true) -f, --fetch Turn on fetching (default: true)
-a, --announce Turn on ref announcing (default: true) -a, --announce Turn on ref announcing (default: true)
-i, --inventory Turn on inventory announcing (default: false) -i, --inventory Turn on inventory announcing (default: false)
--force Force fetches from unknown seeds (default: false)
--timeout <secs> How many seconds to wait while syncing --timeout <secs> How many seconds to wait while syncing
--seed <nid> Sync with the given node (may be specified multiple times) --seed <nid> Sync with the given node (may be specified multiple times)
-r, --replicas <count> Sync with a specific number of seeds -r, --replicas <count> Sync with a specific number of seeds
@ -149,7 +145,6 @@ impl Args for Options {
let mut fetch = false; let mut fetch = false;
let mut announce = false; let mut announce = false;
let mut inventory = false; let mut inventory = false;
let mut force = false;
let mut debug = false; let mut debug = false;
let mut replicas = None; let mut replicas = None;
let mut seeds = BTreeSet::new(); let mut seeds = BTreeSet::new();
@ -167,9 +162,6 @@ impl Args for Options {
Long("fetch") | Short('f') => { Long("fetch") | Short('f') => {
fetch = true; fetch = true;
} }
Long("force") => {
force = true;
}
Long("replicas") | Short('r') => { Long("replicas") | Short('r') => {
let val = parser.value()?; let val = parser.value()?;
let count = term::args::number(&val)?; let count = term::args::number(&val)?;
@ -218,10 +210,8 @@ impl Args for Options {
} }
} }
let sync = if inventory && (fetch || announce || force) { let sync = if inventory && (fetch || announce) {
anyhow::bail!( anyhow::bail!("`--inventory` cannot be used with `--fetch` or `--announce`");
"`--inventory` cannot be used with `--fetch` or `--announce` or `--force`"
);
} else if inventory { } else if inventory {
SyncMode::Inventory SyncMode::Inventory
} else { } else {
@ -230,17 +220,14 @@ impl Args for Options {
(true, false) => SyncDirection::Fetch, (true, false) => SyncDirection::Fetch,
(false, true) => SyncDirection::Announce, (false, true) => SyncDirection::Announce,
}; };
if direction == SyncDirection::Announce && force { let mut settings = SyncSettings::default().timeout(timeout);
anyhow::bail!("`--force` cannot be used without `--fetch`");
}
let settings = if seeds.is_empty() {
SyncSettings::from_replicas(replicas.unwrap_or(3))
} else {
SyncSettings::from_seeds(seeds)
}
.timeout(timeout)
.force(force);
if let Some(r) = replicas {
settings.replicas = r;
}
if !seeds.is_empty() {
settings.seeds = seeds;
}
SyncMode::Repo { SyncMode::Repo {
settings, settings,
direction, direction,
@ -446,33 +433,25 @@ pub fn fetch(
let local = node.nid()?; let local = node.nid()?;
// Get seeds. This consults the local routing table only. // Get seeds. This consults the local routing table only.
let seeds = node.seeds(rid)?; let seeds = node.seeds(rid)?;
// Target replicas, clamped by the maximum replicas possible, let replicas = settings.replicas;
// unless `force` is true.
let replicas = if settings.force {
settings.replicas
} else {
settings
.replicas
.min(seeds.iter().filter(|s| s.nid != local).count())
};
let mut results = FetchResults::default(); let mut results = FetchResults::default();
let (connected, mut disconnected) = seeds.partition(); let (connected, mut disconnected) = seeds.partition();
// Fetch from specified seeds. // Fetch from specified seeds.
for nid in &settings.seeds { for nid in &settings.seeds {
if !seeds.is_connected(nid) && !settings.force { fetch_from(rid, nid, settings.timeout, &mut results, node)?;
term::warning(format!( // We are done when we either hit our replica count,
"node {} is not connected or seeding.. skipping", // or fetch from all the specified seeds.
term::format::node(nid)
));
continue;
}
let result = fetch_from(rid, nid, settings.timeout, node)?;
results.push(*nid, result);
if results.success().count() >= replicas { if results.success().count() >= replicas {
return Ok(results); return Ok(results);
} }
if settings
.seeds
.iter()
.all(|nid| results.get(nid).map_or(false, |r| r.is_success()))
{
return Ok(results);
}
} }
// Fetch from connected seeds. // Fetch from connected seeds.
@ -486,8 +465,7 @@ pub fn fetch(
let Some(nid) = connected.pop_front() else { let Some(nid) = connected.pop_front() else {
break; break;
}; };
let result = fetch_from(rid, &nid, settings.timeout, node)?; fetch_from(rid, &nid, settings.timeout, &mut results, node)?;
results.push(nid, result);
} }
// Try to connect to disconnected seeds and fetch from them. // Try to connect to disconnected seeds and fetch from them.
@ -505,8 +483,7 @@ pub fn fetch(
settings.timeout, settings.timeout,
node, node,
) { ) {
let result = fetch_from(rid, &seed.nid, settings.timeout, node)?; fetch_from(rid, &seed.nid, settings.timeout, &mut results, node)?;
results.push(seed.nid, result);
} }
} }
@ -557,8 +534,9 @@ fn fetch_from(
rid: RepoId, rid: RepoId,
seed: &NodeId, seed: &NodeId,
timeout: time::Duration, timeout: time::Duration,
results: &mut FetchResults,
node: &mut Node, node: &mut Node,
) -> Result<FetchResult, node::Error> { ) -> Result<(), node::Error> {
let spinner = term::spinner(format!( let spinner = term::spinner(format!(
"Fetching {} from {}..", "Fetching {} from {}..",
term::format::tertiary(rid), term::format::tertiary(rid),
@ -574,7 +552,9 @@ fn fetch_from(
spinner.error(reason); spinner.error(reason);
} }
} }
Ok(result) results.push(*seed, result);
Ok(())
} }
fn sort_seeds_by(local: NodeId, seeds: &mut [Seed], aliases: &impl AliasStore, sort_by: &SortBy) { fn sort_seeds_by(local: NodeId, seeds: &mut [Seed], aliases: &impl AliasStore, sort_by: &SortBy) {

View File

@ -22,42 +22,20 @@ pub struct SyncSettings {
pub replicas: usize, pub replicas: usize,
/// Sync with the given list of seeds. /// Sync with the given list of seeds.
pub seeds: BTreeSet<NodeId>, pub seeds: BTreeSet<NodeId>,
/// Sync with the given seeds even if they aren't in our routing table.
/// Can be used to fetch private repositories, for example.
pub force: bool,
/// How long to wait for syncing to complete. /// How long to wait for syncing to complete.
pub timeout: time::Duration, pub timeout: time::Duration,
} }
impl SyncSettings { impl SyncSettings {
/// Create a [`SyncSettings`] from a list of seeds.
pub fn from_seeds(seeds: impl IntoIterator<Item = NodeId>) -> Self {
let seeds = BTreeSet::from_iter(seeds);
Self {
replicas: seeds.len(),
seeds,
force: false,
timeout: DEFAULT_SYNC_TIMEOUT,
}
}
/// Create a [`SyncSettings`] from a replica count.
pub fn from_replicas(replicas: usize) -> Self {
Self {
replicas,
..Self::default()
}
}
/// Set sync timeout. Defaults to [`DEFAULT_SYNC_TIMEOUT`]. /// Set sync timeout. Defaults to [`DEFAULT_SYNC_TIMEOUT`].
pub fn timeout(mut self, timeout: time::Duration) -> Self { pub fn timeout(mut self, timeout: time::Duration) -> Self {
self.timeout = timeout; self.timeout = timeout;
self self
} }
/// Set the 'force' option. /// Set replicas.
pub fn force(mut self, force: bool) -> Self { pub fn replicas(mut self, replicas: usize) -> Self {
self.force = force; self.replicas = replicas;
self self
} }
@ -84,7 +62,6 @@ impl Default for SyncSettings {
Self { Self {
replicas: 3, replicas: 3,
seeds: BTreeSet::new(), seeds: BTreeSet::new(),
force: false,
timeout: DEFAULT_SYNC_TIMEOUT, timeout: DEFAULT_SYNC_TIMEOUT,
} }
} }

View File

@ -242,11 +242,9 @@ pub enum CommandError {
enum TryFetchError<'a> { enum TryFetchError<'a> {
#[error("ongoing fetch for repository exists")] #[error("ongoing fetch for repository exists")]
AlreadyFetching(&'a mut FetchState), AlreadyFetching(&'a mut FetchState),
#[error("session does not exist; cannot initiate fetch")] #[error("peer is not connected; cannot initiate fetch")]
SessionNotFound,
#[error("session is not connected; cannot initiate fetch")]
SessionNotConnected, SessionNotConnected,
#[error("session fetch capacity reached; cannot initiate fetch")] #[error("peer fetch capacity reached; cannot initiate fetch")]
SessionCapacityReached, SessionCapacityReached,
#[error(transparent)] #[error(transparent)]
Namespaces(#[from] NamespacesError), Namespaces(#[from] NamespacesError),
@ -997,7 +995,7 @@ where
) -> Result<&mut FetchState, TryFetchError> { ) -> Result<&mut FetchState, TryFetchError> {
let from = *from; let from = *from;
let Some(session) = self.sessions.get_mut(&from) else { let Some(session) = self.sessions.get_mut(&from) else {
return Err(TryFetchError::SessionNotFound); return Err(TryFetchError::SessionNotConnected);
}; };
let fetching = self.fetching.entry(rid); let fetching = self.fetching.entry(rid);

View File

@ -775,6 +775,11 @@ impl FetchResults {
self.0.iter().any(|(n, _)| n == nid) self.0.iter().any(|(n, _)| n == nid)
} }
/// Get a node's result.
pub fn get(&self, nid: &NodeId) -> Option<&FetchResult> {
self.0.iter().find(|(n, _)| n == nid).map(|(_, r)| r)
}
/// Iterate over all fetch results. /// Iterate over all fetch results.
pub fn iter(&self) -> impl Iterator<Item = (&NodeId, &FetchResult)> { pub fn iter(&self) -> impl Iterator<Item = (&NodeId, &FetchResult)> {
self.0.iter().map(|(nid, r)| (nid, r)) self.0.iter().map(|(nid, r)| (nid, r))