diff options
Diffstat (limited to 'lib/entry/libimagentrylink')
-rw-r--r-- | lib/entry/libimagentrylink/src/iter.rs | 97 | ||||
-rw-r--r-- | lib/entry/libimagentrylink/src/lib.rs | 5 | ||||
-rw-r--r-- | lib/entry/libimagentrylink/src/link.rs | 150 | ||||
-rw-r--r-- | lib/entry/libimagentrylink/src/linker.rs (renamed from lib/entry/libimagentrylink/src/internal.rs) | 365 | ||||
-rw-r--r-- | lib/entry/libimagentrylink/src/storecheck.rs | 173 |
5 files changed, 427 insertions, 363 deletions
diff --git a/lib/entry/libimagentrylink/src/iter.rs b/lib/entry/libimagentrylink/src/iter.rs new file mode 100644 index 00000000..5d76d0b6 --- /dev/null +++ b/lib/entry/libimagentrylink/src/iter.rs @@ -0,0 +1,97 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer <mail@beyermatthias.de> and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + + +use std::vec::IntoIter; + +use failure::Error; +use failure::ResultExt; +use failure::Fallible as Result; +use toml::Value; +use itertools::Itertools; + +use libimagstore::store::Store; +use libimagstore::store::FileLockEntry; +use libimagerror::errors::ErrorMsg as EM; + +use crate::link::Link; + +pub struct LinkIter(IntoIter<Link>); + +impl LinkIter { + + pub fn new(v: Vec<Link>) -> LinkIter { + LinkIter(v.into_iter()) + } + + pub fn into_getter(self, store: &Store) -> GetIter { + GetIter(self.0, store) + } + +} + +impl Iterator for LinkIter { + type Item = Link; + + fn next(&mut self) -> Option<Self::Item> { + self.0.next() + } +} + +pub trait IntoValues { + fn into_values(self) -> Vec<Result<Value>>; +} + +impl<I: Iterator<Item = Link>> IntoValues for I { + fn into_values(self) -> Vec<Result<Value>> { + self.map(|s| s.without_base()) + .unique() + .sorted() + .into_iter() // Cannot sort toml::Value, hence uglyness here + .map(|link| link.to_value().context(EM::ConversionError).map_err(Error::from)) + .collect() + } +} + +/// An Iterator that `Store::get()`s the Entries from the store while consumed +pub struct GetIter<'a>(IntoIter<Link>, &'a Store); + +impl<'a> GetIter<'a> { + pub fn new(i: IntoIter<Link>, store: &'a Store) -> GetIter<'a> { + GetIter(i, store) + } + + pub fn store(&self) -> &Store { + self.1 + } +} + +impl<'a> Iterator for GetIter<'a> { + type Item = Result<FileLockEntry<'a>>; + + fn next(&mut self) -> Option<Self::Item> { + self.0.next().and_then(|id| match self.1.get(id) { + Ok(None) => None, + Ok(Some(x)) => Some(Ok(x)), + Err(e) => Some(Err(e).map_err(From::from)), + }) + } + +} + diff --git a/lib/entry/libimagentrylink/src/lib.rs b/lib/entry/libimagentrylink/src/lib.rs index 992d3dd2..84fcba78 100644 --- a/lib/entry/libimagentrylink/src/lib.rs +++ b/lib/entry/libimagentrylink/src/lib.rs @@ -56,5 +56,8 @@ extern crate libimagutil; module_entry_path_mod!("links"); -pub mod internal; +pub mod iter; +pub mod linker; +pub mod link; +pub mod storecheck; diff --git a/lib/entry/libimagentrylink/src/link.rs b/lib/entry/libimagentrylink/src/link.rs new file mode 100644 index 00000000..eb594877 --- /dev/null +++ b/lib/entry/libimagentrylink/src/link.rs @@ -0,0 +1,150 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer <mail@beyermatthias.de> and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use libimagstore::storeid::StoreId; +use libimagstore::storeid::IntoStoreId; +use libimagstore::store::Store; +use libimagerror::errors::ErrorMsg as EM; + +use toml::Value; +use toml::map::Map; +use failure::ResultExt; +use failure::Fallible as Result; +use failure::Error; + +#[derive(Eq, PartialOrd, Ord, Hash, Debug, Clone)] +pub enum Link { + Id { link: StoreId }, + Annotated { link: StoreId, annotation: String }, +} + +impl Link { + + pub fn exists(&self, store: &Store) -> Result<bool> { + match *self { + Link::Id { ref link } => store.exists(link.clone()), + Link::Annotated { ref link, .. } => store.exists(link.clone()), + } + .map_err(From::from) + } + + pub fn to_str(&self) -> Result<String> { + match *self { + Link::Id { ref link } => link.to_str(), + Link::Annotated { ref link, .. } => link.to_str(), + } + .map_err(From::from) + } + + + pub(crate) fn eq_store_id(&self, id: &StoreId) -> bool { + match self { + &Link::Id { link: ref s } => s.eq(id), + &Link::Annotated { link: ref s, .. } => s.eq(id), + } + } + + /// Get the StoreId inside the Link, which is always present + pub fn get_store_id(&self) -> &StoreId { + match self { + &Link::Id { link: ref s } => s, + &Link::Annotated { link: ref s, .. } => s, + } + } + + /// Helper wrapper around Link for StoreId + pub(crate) fn without_base(self) -> Link { + match self { + Link::Id { link: s } => Link::Id { link: s }, + Link::Annotated { link: s, annotation: ann } => + Link::Annotated { link: s, annotation: ann }, + } + } + + pub(crate) fn to_value(&self) -> Result<Value> { + match self { + &Link::Id { link: ref s } => + s.to_str() + .map(Value::String) + .context(EM::ConversionError) + .map_err(Error::from), + &Link::Annotated { ref link, annotation: ref anno } => { + link.to_str() + .map(Value::String) + .context(EM::ConversionError) + .map_err(Error::from) + .map(|link| { + let mut tab = Map::new(); + + tab.insert("link".to_owned(), link); + tab.insert("annotation".to_owned(), Value::String(anno.clone())); + Value::Table(tab) + }) + } + } + } + +} + +impl ::std::cmp::PartialEq for Link { + fn eq(&self, other: &Self) -> bool { + match (self, other) { + (&Link::Id { link: ref a }, &Link::Id { link: ref b }) => a.eq(&b), + (&Link::Annotated { link: ref a, annotation: ref ann1 }, + &Link::Annotated { link: ref b, annotation: ref ann2 }) => + (a, ann1).eq(&(b, ann2)), + _ => false, + } + } +} + +impl From<StoreId> for Link { + + fn from(s: StoreId) -> Link { + Link::Id { link: s } + } +} + +impl Into<StoreId> for Link { + fn into(self) -> StoreId { + match self { + Link::Id { link } => link, + Link::Annotated { link, .. } => link, + } + } +} + +impl IntoStoreId for Link { + fn into_storeid(self) -> Result<StoreId> { + match self { + Link::Id { link } => Ok(link), + Link::Annotated { link, .. } => Ok(link), + } + } +} + +impl AsRef<StoreId> for Link { + fn as_ref(&self) -> &StoreId { + match self { + &Link::Id { ref link } => &link, + &Link::Annotated { ref link, .. } => &link, + } + } +} + diff --git a/lib/entry/libimagentrylink/src/internal.rs b/lib/entry/libimagentrylink/src/linker.rs index ca24201f..a94a5707 100644 --- a/lib/entry/libimagentrylink/src/internal.rs +++ b/lib/entry/libimagentrylink/src/linker.rs @@ -18,144 +18,23 @@ // use libimagstore::storeid::StoreId; -use libimagstore::storeid::IntoStoreId; use libimagstore::store::Entry; use libimagstore::store::Store; use libimagerror::errors::ErrorMsg as EM; use toml_query::read::TomlValueReadExt; use toml_query::insert::TomlValueInsertExt; -use toml::map::Map; use failure::ResultExt; use failure::Fallible as Result; use failure::Error; use failure::err_msg; -use self::iter::LinkIter; -use self::iter::IntoValues; +use crate::iter::LinkIter; +use crate::iter::IntoValues; +use crate::link::Link; use toml::Value; -#[derive(Eq, PartialOrd, Ord, Hash, Debug, Clone)] -pub enum Link { - Id { link: StoreId }, - Annotated { link: StoreId, annotation: String }, -} - -impl Link { - - pub fn exists(&self, store: &Store) -> Result<bool> { - match *self { - Link::Id { ref link } => store.exists(link.clone()), - Link::Annotated { ref link, .. } => store.exists(link.clone()), - } - .map_err(From::from) - } - - pub fn to_str(&self) -> Result<String> { - match *self { - Link::Id { ref link } => link.to_str(), - Link::Annotated { ref link, .. } => link.to_str(), - } - .map_err(From::from) - } - - - fn eq_store_id(&self, id: &StoreId) -> bool { - match self { - &Link::Id { link: ref s } => s.eq(id), - &Link::Annotated { link: ref s, .. } => s.eq(id), - } - } - - /// Get the StoreId inside the Link, which is always present - pub fn get_store_id(&self) -> &StoreId { - match self { - &Link::Id { link: ref s } => s, - &Link::Annotated { link: ref s, .. } => s, - } - } - - /// Helper wrapper around Link for StoreId - fn without_base(self) -> Link { - match self { - Link::Id { link: s } => Link::Id { link: s }, - Link::Annotated { link: s, annotation: ann } => - Link::Annotated { link: s, annotation: ann }, - } - } - - fn to_value(&self) -> Result<Value> { - match self { - &Link::Id { link: ref s } => - s.to_str() - .map(Value::String) - .context(EM::ConversionError) - .map_err(Error::from), - &Link::Annotated { ref link, annotation: ref anno } => { - link.to_str() - .map(Value::String) - .context(EM::ConversionError) - .map_err(Error::from) - .map(|link| { - let mut tab = Map::new(); - - tab.insert("link".to_owned(), link); - tab.insert("annotation".to_owned(), Value::String(anno.clone())); - Value::Table(tab) - }) - } - } - } - -} - -impl ::std::cmp::PartialEq for Link { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (&Link::Id { link: ref a }, &Link::Id { link: ref b }) => a.eq(&b), - (&Link::Annotated { link: ref a, annotation: ref ann1 }, - &Link::Annotated { link: ref b, annotation: ref ann2 }) => - (a, ann1).eq(&(b, ann2)), - _ => false, - } - } -} - -impl From<StoreId> for Link { - - fn from(s: StoreId) -> Link { - Link::Id { link: s } - } -} - -impl Into<StoreId> for Link { - fn into(self) -> StoreId { - match self { - Link::Id { link } => link, - Link::Annotated { link, .. } => link, - } - } -} - -impl IntoStoreId for Link { - fn into_storeid(self) -> Result<StoreId> { - match self { - Link::Id { link } => Ok(link), - Link::Annotated { link, .. } => Ok(link), - } - } -} - -impl AsRef<StoreId> for Link { - fn as_ref(&self) -> &StoreId { - match self { - &Link::Id { ref link } => &link, - &Link::Annotated { ref link, .. } => &link, - } - } -} - pub trait InternalLinker { /// Get the internal links from the implementor object @@ -174,85 +53,6 @@ pub trait InternalLinker { fn add_internal_annotated_link(&mut self, link: &mut Entry, annotation: String) -> Result<()>; } -pub mod iter { - use std::vec::IntoIter; - use super::Link; - - use failure::Error; - use failure::Fallible as Result; - use failure::ResultExt; - use toml::Value; - use itertools::Itertools; - - use libimagstore::store::Store; - use libimagstore::store::FileLockEntry; - use libimagerror::errors::ErrorMsg as EM; - - pub struct LinkIter(IntoIter<Link>); - - impl LinkIter { - - pub fn new(v: Vec<Link>) -> LinkIter { - LinkIter(v.into_iter()) - } - - pub fn into_getter(self, store: &Store) -> GetIter { - GetIter(self.0, store) - } - - } - - impl Iterator for LinkIter { - type Item = Link; - - fn next(&mut self) -> Option<Self::Item> { - self.0.next() - } - } - - pub trait IntoValues { - fn into_values(self) -> Vec<Result<Value>>; - } - - impl<I: Iterator<Item = Link>> IntoValues for I { - fn into_values(self) -> Vec<Result<Value>> { - self.map(|s| s.without_base()) - .unique() - .sorted() - .into_iter() // Cannot sort toml::Value, hence uglyness here - .map(|link| link.to_value().context(EM::ConversionError).map_err(Error::from)) - .collect() - } - } - - /// An Iterator that `Store::get()`s the Entries from the store while consumed - pub struct GetIter<'a>(IntoIter<Link>, &'a Store); - - impl<'a> GetIter<'a> { - pub fn new(i: IntoIter<Link>, store: &'a Store) -> GetIter<'a> { - GetIter(i, store) - } - - pub fn store(&self) -> &Store { - self.1 - } - } - - impl<'a> Iterator for GetIter<'a> { - type Item = Result<FileLockEntry<'a>>; - - fn next(&mut self) -> Option<Self::Item> { - self.0.next().and_then(|id| match self.1.get(id) { - Ok(None) => None, - Ok(Some(x)) => Some(Ok(x)), - Err(e) => Some(Err(e).map_err(From::from)), - }) - } - - } - -} - impl InternalLinker for Entry { fn get_internal_links(&self) -> Result<LinkIter> { @@ -452,164 +252,6 @@ fn process_rw_result(links: Result<Option<Value>>) -> Result<LinkIter> { Ok(LinkIter::new(links)) } -pub mod store_check { - use libimagstore::store::Store; - - use failure::ResultExt; - use failure::Fallible as Result; - use failure::Error; - use failure::err_msg; - - pub trait StoreLinkConsistentExt { - fn check_link_consistency(&self) -> Result<()>; - } - - impl StoreLinkConsistentExt for Store { - fn check_link_consistency(&self) -> Result<()> { - use std::collections::HashMap; - - use crate::internal::InternalLinker; - - use libimagstore::storeid::StoreId; - use libimagutil::debug_result::DebugResult; - - // Helper data structure to collect incoming and outgoing links for each StoreId - #[derive(Debug, Default)] - struct Linking { - outgoing: Vec<StoreId>, - incoming: Vec<StoreId>, - } - - // Helper function to aggregate the Link network - // - // This function aggregates a HashMap which maps each StoreId object in the store onto - // a Linking object, which contains a list of StoreIds which this entry links to and a - // list of StoreIds which link to the current one. - // - // The lambda returns an error if something fails - let aggregate_link_network = |store: &Store| -> Result<HashMap<StoreId, Linking>> { - store - .entries()? - .into_get_iter() - .fold(Ok(HashMap::new()), |map, element| { - map.and_then(|mut map| { - debug!("Checking element = {:?}", element); - let entry = element?.ok_or_else(|| err_msg("TODO: Not yet handled"))?; - - debug!("Checking entry = {:?}", entry.get_location()); - - let internal_links = entry - .get_internal_links()? - .into_getter(store); // get the FLEs from the Store - - let mut linking = Linking::default(); - for internal_link in internal_links { - debug!("internal link = {:?}", internal_link); - - linking.outgoing.push(internal_link?.get_location().clone()); - linking.incoming.push(entry.get_location().clone()); - } - - map.insert(entry.get_location().clone(), linking); - Ok(map) - }) - }) - }; - - // Helper to check whethre all StoreIds in the network actually exists - // - // Because why not? - let all_collected_storeids_exist = |network: &HashMap<StoreId, Linking>| -> Result<()> { - for (id, _) in network.iter() { - if is_match!(self.get(id.clone()), Ok(Some(_))) { - debug!("Exists in store: {:?}", id); - - if !self.exists(id.clone())? { - warn!("Does exist in store but not on FS: {:?}", id); - return Err(err_msg("Link target does not exist")) - } - } else { - warn!("Does not exist in store: {:?}", id); - return Err(err_msg("Link target does not exist")) - } - } - - Ok(()) - }; - - // Helper function to create a SLCECD::OneDirectionalLink error object - let mk_one_directional_link_err = |src: StoreId, target: StoreId| -> Error { - Error::from(format_err!("Dead link: {} -> {}", - src.local_display_string(), - target.local_display_string())) - }; - - // Helper lambda to check whether the _incoming_ links of each entry actually also - // appear in the _outgoing_ list of the linked entry - let incoming_links_exists_as_outgoing_links = - |src: &StoreId, linking: &Linking, network: &HashMap<StoreId, Linking>| -> Result<()> { - for link in linking.incoming.iter() { - // Check whether the links which are _incoming_ on _src_ are outgoing - // in each of the links in the incoming list. - let incoming_consistent = network.get(link) - .map(|l| l.outgoing.contains(src)) - .unwrap_or(false); - - if !incoming_consistent { - return Err(mk_one_directional_link_err(src.clone(), link.clone())) - } - } - - Ok(()) - }; - - // Helper lambda to check whether the _outgoing links of each entry actually also - // appear in the _incoming_ list of the linked entry - let outgoing_links_exist_as_incoming_links = - |src: &StoreId, linking: &Linking, network: &HashMap<StoreId, Linking>| -> Result<()> { - for link in linking.outgoing.iter() { - // Check whether the links which are _outgoing_ on _src_ are incoming - // in each of the links in the outgoing list. - let outgoing_consistent = network.get(link) - .map(|l| l.incoming.contains(src)) - .unwrap_or(false); - - if !outgoing_consistent { - return Err(mk_one_directional_link_err(link.clone(), src.clone())) - } - } - - Ok(()) - }; - - aggregate_link_network(&self) - .map_dbg_str("Aggregated") - .map_dbg(|nw| { - let mut s = String::new(); - for (k, v) in nw { - s.push_str(&format!("{}\n in: {:?}\n out: {:?}", k, v.incoming, v.outgoing)); - } - s - }) - .and_then(|nw| { - all_collected_storeids_exist(&nw) - .map(|_| nw) - .context(err_msg("Link handling error")) - .map_err(Error::from) - }) - .and_then(|nw| { - for (id, linking) in nw.iter() { - incoming_links_exists_as_outgoing_links(id, linking, &nw)?; - outgoing_links_exist_as_incoming_links(id, linking, &nw)?; - } - Ok(()) - }) - .map(|_| ()) - } - } - -} - #[cfg(test)] mod test { use std::path::PathBuf; @@ -852,4 +494,3 @@ mod test { } } - diff --git a/lib/entry/libimagentrylink/src/storecheck.rs b/lib/entry/libimagentrylink/src/storecheck.rs new file mode 100644 index 00000000..decf4cdd --- /dev/null +++ b/lib/entry/libimagentrylink/src/storecheck.rs @@ -0,0 +1,173 @@ +// +// imag - the personal information management suite for the commandline +// Copyright (C) 2015-2019 Matthias Beyer <mail@beyermatthias.de> and contributors +// +// This library is free software; you can redistribute it and/or +// modify it under the terms of the GNU Lesser General Public +// License as published by the Free Software Foundation; version +// 2.1 of the License. +// +// This library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public +// License along with this library; if not, write to the Free Software +// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +// + +use std::collections::HashMap; + +use libimagstore::store::Store; +use libimagstore::storeid::StoreId; +use libimagutil::debug_result::DebugResult; + +use failure::ResultExt; +use failure::Fallible as Result; +use failure::Error; +use failure::err_msg; + +use crate::linker::*; + +pub trait StoreLinkConsistentExt { + fn check_link_consistency(&self) -> Result<()>; +} + +impl StoreLinkConsistentExt for Store { + fn check_link_consistency(&self) -> Result<()> { + // Helper data structure to collect incoming and outgoing links for each StoreId + #[derive(Debug, Default)] + struct Linking { + outgoing: Vec<StoreId>, + incoming: Vec<StoreId>, + } + + // Helper function to aggregate the Link network + // + // This function aggregates a HashMap which maps each StoreId object in the store onto + // a Linking object, which contains a list of StoreIds which this entry links to and a + // list of StoreIds which link to the current one. + // + // The lambda returns an error if something fails + let aggregate_link_network = |store: &Store| -> Result<HashMap<StoreId, Linking>> { + store + .entries()? + .into_get_iter() + .fold(Ok(HashMap::new()), |map, element| { + map.and_then(|mut map| { + debug!("Checking element = {:?}", element); + let entry = element?.ok_or_else(|| err_msg("TODO: Not yet handled"))?; + + debug!("Checking entry = {:?}", entry.get_location()); + + let internal_links = entry + .get_internal_links()? + .into_getter(store); // get the FLEs from the Store + + let mut linking = Linking::default(); + for internal_link in internal_links { + debug!("internal link = {:?}", internal_link); + + linking.outgoing.push(internal_link?.get_location().clone()); + linking.incoming.push(entry.get_location().clone()); + } + + map.insert(entry.get_location().clone(), linking); + Ok(map) + }) + }) + }; + + // Helper to check whethre all StoreIds in the network actually exists + // + // Because why not? + let all_collected_storeids_exist = |network: &HashMap<StoreId, Linking>| -> Result<()> { + for (id, _) in network.iter() { + if is_match!(self.get(id.clone()), Ok(Some(_))) { + debug!("Exists in store: {:?}", id); + + if !self.exists(id.clone())? { + warn!("Does exist in store but not on FS: {:?}", id); + return Err(err_msg("Link target does not exist")) + } + } else { + warn!("Does not exist in store: {:?}", id); + return Err(err_msg("Link target does not exist")) + } + } + + Ok(()) + }; + + // Helper function to create a SLCECD::OneDirectionalLink error object + let mk_one_directional_link_err = |src: StoreId, target: StoreId| -> Error { + Error::from(format_err!("Dead link: {} -> {}", + src.local_display_string(), + target.local_display_string())) + }; + + // Helper lambda to check whether the _incoming_ links of each entry actually also + // appear in the _outgoing_ list of the linked entry + let incoming_links_exists_as_outgoing_links = + |src: &StoreId, linking: &Linking, network: &HashMap<StoreId, Linking>| -> Result<()> { + for link in linking.incoming.iter() { + // Check whether the links which are _incoming_ on _src_ are outgoing + // in each of the links in the incoming list. + let incoming_consistent = network.get(link) + .map(|l| l.outgoing.contains(src)) + .unwrap_or(false); + + if !incoming_consistent { + return Err(mk_one_directional_link_err(src.clone(), link.clone())) + } + } + + Ok(()) + }; + + // Helper lambda to check whether the _outgoing links of each entry actually also + // appear in the _incoming_ list of the linked entry + let outgoing_links_exist_as_incoming_links = + |src: &StoreId, linking: &Linking, network: &HashMap<StoreId, Linking>| -> Result<()> { + for link in linking.outgoing.iter() { + // Check whether the links which are _outgoing_ on _src_ are incoming + // in each of the links in the outgoing list. + let outgoing_consistent = network.get(link) + .map(|l| l.incoming.contains(src)) + .unwrap_or(false); + + if !outgoing_consistent { + return Err(mk_one_directional_link_err(link.clone(), src.clone())) + } + } + + Ok(()) + }; + + aggregate_link_network(&self) + .map_dbg_str("Aggregated") + .map_dbg(|nw| { + let mut s = String::new(); + for (k, v) in nw { + s.push_str(&format!("{}\n in: {:?}\n out: {:?}", k, v.incoming, v.outgoing)); + } + s + }) + .and_then(|nw| { + all_collected_storeids_exist(&nw) + .map(|_| nw) + .context(err_msg("Link handling error")) + .map_err(Error::from) + }) + .and_then(|nw| { + for (id, linking) in nw.iter() { + incoming_links_exists_as_outgoing_links(id, linking, &nw)?; + outgoing_links_exist_as_incoming_links(id, linking, &nw)?; + } + Ok(()) + }) + .map(|_| ()) + } +} + |