summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/async_dag.rs542
-rw-r--r--src/dag_backend.rs106
-rw-r--r--src/error.rs11
-rw-r--r--src/id.rs18
-rw-r--r--src/lib.rs20
-rw-r--r--src/node.rs36
-rw-r--r--src/node_id.rs10
-rw-r--r--src/repository.rs26
-rw-r--r--src/test_impl.rs79
9 files changed, 770 insertions, 78 deletions
diff --git a/src/async_dag.rs b/src/async_dag.rs
new file mode 100644
index 0000000..7abc6c5
--- /dev/null
+++ b/src/async_dag.rs
@@ -0,0 +1,542 @@
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+//
+
+use std::pin::Pin;
+
+use futures::stream::StreamExt;
+use futures::task::Poll;
+
+use crate::DagBackend;
+use crate::Node;
+use crate::NodeId;
+
+/// An async DAG, generic over Node, Node identifier and Backend implementation
+pub struct AsyncDag<Id, N, Backend>
+where
+ Id: NodeId + Send,
+ N: Node<Id = Id>,
+ Backend: DagBackend<Id, N>,
+{
+ head: Id,
+ backend: Backend,
+ _node: std::marker::PhantomData<N>,
+}
+
+impl<Id, N, Backend, Error> AsyncDag<Id, N, Backend>
+where
+ Id: NodeId + Send,
+ N: Node<Id = Id>,
+ Backend: DagBackend<Id, N, Error = Error>,
+ Error: From<crate::Error<Id>>,
+{
+ /// Create a new DAG with a backend and a HEAD node
+ ///
+ /// The head node is `DagBackend::put` into the backend before the AsyncDag object is created.
+ pub async fn new(mut backend: Backend, head: N) -> Result<Self, Error> {
+ backend.put(head).await.map(|id| AsyncDag {
+ head: id,
+ backend,
+ _node: std::marker::PhantomData,
+ })
+ }
+
+ /// Load a AsyncDag object using `head` as HEAD node.
+ ///
+ /// # Warning
+ ///
+ /// This fails if backend.get(head) fails.
+ pub async fn load(backend: Backend, head: Id) -> Result<Self, Error> {
+ backend
+ .get(head.clone())
+ .await?
+ .map(|(id, _)| AsyncDag {
+ head: id,
+ backend,
+ _node: std::marker::PhantomData,
+ })
+ .ok_or_else(|| crate::Error::NodeNotFound(head).into())
+ }
+
+ pub fn head(&self) -> &Id {
+ &self.head
+ }
+
+ pub fn backend(&self) -> &Backend {
+ &self.backend
+ }
+
+ pub fn backend_mut(&mut self) -> &mut Backend {
+ &mut self.backend
+ }
+
+ /// Check whether an `id` is in the DAG.
+ pub async fn has_id(&self, id: &Id) -> Result<bool, Error> {
+ self.stream()
+ .map(|r| -> Result<bool, _> { r.map(|(ref node_id, _)| node_id == id) })
+ .collect::<Vec<Result<bool, _>>>()
+ .await
+ .into_iter()
+ .fold(Ok(false), |acc, e| match (acc, e) {
+ (Err(e), _) => Err(e),
+ (Ok(_), Err(e)) => Err(e),
+ (Ok(a), Ok(b)) => Ok(a || b),
+ })
+ }
+
+ /// Iterate over the DAG
+ ///
+ /// This function returns a Stream over all nodes in the DAG.
+ ///
+ /// # Warning
+ ///
+ /// The order of the nodes is not (yet) guaranteed.
+ pub fn stream(&self) -> Stream<Id, N, Backend, Error> {
+ Stream {
+ dag: self,
+ backlog: {
+ let mut v = Vec::with_capacity(2);
+ v.push(self.backend.get(self.head.clone()));
+ v
+ },
+ }
+ }
+
+ /// Update the HEAD pointer of the DAG
+ ///
+ /// # Warning
+ ///
+ /// fails if the passed node does not point to the current HEAD in its parents.
+ pub async fn update_head(&mut self, node: N) -> Result<Id, Error> {
+ if node.parent_ids().iter().any(|id| id == &self.head) {
+ self.update_head_unchecked(node).await
+ } else {
+ Err(Error::from(crate::Error::HeadNotAParent))
+ }
+ }
+
+ /// Update the HEAD pointer of the DAG, unchecked
+ ///
+ /// # Warning
+ ///
+ /// Does not check whether the passed `node` does point to the current (then old) HEAD in its
+ /// parents. Be careful to not lose nodes from the DAG with this.
+ pub async fn update_head_unchecked(&mut self, node: N) -> Result<Id, Error> {
+ let id = self.backend.put(node).await?;
+ self.head = id.clone();
+ Ok(id)
+ }
+
+ /// Branch from the current head.
+ ///
+ /// This function creates a new AsyncDag object with the same backend (thus the backend must be
+ /// `Clone` in this case).
+ pub fn branch(&self) -> AsyncDag<Id, N, Backend>
+ where
+ Backend: Clone,
+ {
+ AsyncDag {
+ head: self.head.clone(),
+ backend: self.backend.clone(),
+ _node: std::marker::PhantomData,
+ }
+ }
+
+ /// Merge another AsyncDag into this one
+ ///
+ /// Use the `merger` function to merge the two head IDs and generate a new Node instance for
+ /// the new HEAD of `self`.
+ pub async fn merge<M>(
+ &mut self,
+ other: &AsyncDag<Id, N, Backend>,
+ merger: M,
+ ) -> Result<Id, Error>
+ where
+ M: Merger<Id, N, Error = Error>,
+ {
+ let node = merger.create_merge_node(&self.head, &other.head)?;
+ let id = self.backend.put(node).await?;
+ self.head = id.clone();
+ Ok(id)
+ }
+}
+
+pub trait Merger<Id, N>
+where
+ Id: NodeId,
+ N: Node<Id = Id>,
+{
+ type Error;
+
+ fn create_merge_node(&self, left_id: &Id, right_id: &Id) -> Result<N, Self::Error>;
+}
+
+impl<Id, N, F, Error> Merger<Id, N> for F
+where
+ Id: NodeId,
+ N: Node<Id = Id>,
+ F: Fn(&Id, &Id) -> Result<N, Error>,
+ Error: From<crate::Error<Id>>,
+{
+ type Error = Error;
+
+ fn create_merge_node(&self, left_id: &Id, right_id: &Id) -> Result<N, Self::Error> {
+ (self)(left_id, right_id)
+ }
+}
+
+/// Stream adapter for streaming all nodes in a DAG
+pub struct Stream<'a, Id, N, Backend, Error>
+where
+ Id: NodeId + Send,
+ N: Node<Id = Id>,
+ Backend: DagBackend<Id, N>,
+ Error: From<crate::Error<Id>>,
+{
+ dag: &'a AsyncDag<Id, N, Backend>,
+ backlog: Vec<Pin<Backlog<'a, Id, N, Error>>>,
+}
+
+pub type Backlog<'a, Id, N, Error> = Box<
+ (dyn futures::future::Future<Output = Result<Option<(Id, N)>, Error>> + std::marker::Send + 'a),
+>;
+
+impl<'a, Id, N, Backend, Error> futures::stream::Stream for Stream<'a, Id, N, Backend, Error>
+where
+ Id: NodeId + Send,
+ N: Node<Id = Id>,
+ Backend: DagBackend<Id, N, Error = Error>,
+ Error: From<crate::Error<Id>>,
+{
+ type Item = Result<(Id, N), Error>;
+
+ fn poll_next(
+ mut self: std::pin::Pin<&mut Self>,
+ cx: &mut futures::task::Context<'_>,
+ ) -> futures::task::Poll<Option<Self::Item>> {
+ if let Some(mut fut) = self.as_mut().backlog.pop() {
+ match fut.as_mut().poll(cx) {
+ Poll::Ready(Err(e)) => Poll::Ready(Some(Err(e))),
+ Poll::Ready(Ok(Some((node_id, node)))) => {
+ for parent in node.parent_ids().into_iter() {
+ let fut = self.dag.backend.get(parent.clone());
+ self.as_mut().backlog.push(fut);
+ }
+ Poll::Ready(Some(Ok((node_id, node))))
+ }
+ Poll::Ready(Ok(None)) => {
+ // backend.get() returned Ok(None), so the referenced node seems not to exist
+ //
+ // TODO: Decide whether we should return an error here.
+ cx.waker().wake_by_ref();
+ Poll::Pending
+ }
+ Poll::Pending => {
+ cx.waker().wake_by_ref();
+ Poll::Pending
+ }
+ }
+ } else {
+ Poll::Ready(None)
+ }
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use futures::StreamExt;
+
+ use crate::test_impl as test;
+ use crate::test_impl::TestError;
+ use crate::AsyncDag;
+ use crate::DagBackend;
+
+ #[test]
+ fn test_dag_two_nodes() {
+ let head = test::Node {
+ parents: vec![test::Id(0)],
+ data: 1,
+ };
+
+ let b = test::Backend::new(vec![
+ {
+ Some(test::Node {
+ parents: vec![],
+ data: 0,
+ })
+ },
+ { Some(head.clone()) },
+ ]);
+
+ {
+ let node = tokio_test::block_on(b.get(test::Id(1))).unwrap().unwrap();
+ assert_eq!(node.1.data, 1);
+ assert!(!node.1.parents.is_empty()); // to check whether the parent is set
+ }
+
+ let dag = tokio_test::block_on(AsyncDag::new(b, head));
+ assert!(dag.is_ok());
+ let dag = dag.unwrap();
+
+ {
+ let has_id = tokio_test::block_on(dag.has_id(&test::Id(0)));
+ assert!(has_id.is_ok());
+ let has_id = has_id.unwrap();
+ assert!(has_id);
+ }
+ {
+ let has_id = tokio_test::block_on(dag.has_id(&test::Id(1)));
+ assert!(has_id.is_ok());
+ let has_id = has_id.unwrap();
+ assert!(has_id);
+ }
+ }
+
+ #[test]
+ fn test_dag_two_nodes_stream() {
+ let head = test::Node {
+ parents: vec![test::Id(0)],
+ data: 1,
+ };
+
+ let b = test::Backend::new(vec![
+ {
+ Some(test::Node {
+ parents: vec![],
+ data: 0,
+ })
+ },
+ { Some(head.clone()) },
+ ]);
+
+ let dag = tokio_test::block_on(AsyncDag::new(b, head));
+ assert!(dag.is_ok());
+ let dag = dag.unwrap();
+
+ let v = tokio_test::block_on(dag.stream().collect::<Vec<_>>());
+
+ assert_eq!(v.len(), 2, "Expected two nodes: {:?}", v);
+ assert_eq!(v[0].as_ref().unwrap().0, test::Id(1));
+ assert_eq!(v[1].as_ref().unwrap().0, test::Id(0));
+ }
+
+ #[test]
+ fn test_adding_head() {
+ let head = test::Node {
+ parents: vec![],
+ data: 0,
+ };
+ let b = test::Backend::new(vec![Some(head.clone())]);
+
+ let dag = tokio_test::block_on(AsyncDag::new(b, head));
+ assert!(dag.is_ok());
+ let mut dag = dag.unwrap();
+
+ let new_head = test::Node {
+ parents: vec![test::Id(0)],
+ data: 1,
+ };
+
+ assert_eq!(dag.backend.0.read().unwrap().len(), 1);
+ assert_eq!(dag.head, test::Id(0));
+
+ let id = tokio_test::block_on(dag.update_head(new_head));
+ assert!(id.is_ok());
+ let _ = id.unwrap();
+
+ assert_eq!(dag.backend.0.read().unwrap().len(), 2);
+ assert_eq!(dag.head, test::Id(1));
+
+ assert_eq!(dag.backend.0.read().unwrap()[0].as_ref().unwrap().data, 0);
+ assert!(dag.backend.0.read().unwrap()[0]
+ .as_ref()
+ .unwrap()
+ .parents
+ .is_empty());
+
+ assert_eq!(dag.backend.0.read().unwrap()[1].as_ref().unwrap().data, 1);
+ assert_eq!(
+ dag.backend.0.read().unwrap()[1]
+ .as_ref()
+ .unwrap()
+ .parents
+ .len(),
+ 1
+ );
+ assert_eq!(
+ dag.backend.0.read().unwrap()[1].as_ref().unwrap().parents[0],
+ test::Id(0)
+ );
+ }
+
+ #[test]
+ fn test_branch() {
+ let mut dag = {
+ let head = test::Node {
+ parents: vec![],
+ data: 0,
+ };
+ let b = test::Backend::new(vec![Some(head.clone())]);
+ let dag = tokio_test::block_on(AsyncDag::new(b, head));
+ assert!(dag.is_ok());
+ dag.unwrap()
+ };
+
+ let branched = dag.branch();
+
+ {
+ assert_eq!(dag.backend.0.read().unwrap().len(), 1);
+ assert_eq!(dag.head, test::Id(0));
+ let new_head = test::Node {
+ parents: vec![test::Id(0)],
+ data: 1,
+ };
+
+ let id = tokio_test::block_on(dag.update_head(new_head));
+ assert!(id.is_ok());
+ let _ = id.unwrap();
+
+ assert_eq!(dag.backend.0.read().unwrap().len(), 2);
+ assert_eq!(dag.head, test::Id(1));
+ }
+
+ assert_eq!(branched.backend.0.read().unwrap().len(), 2);
+ assert_eq!(branched.head, test::Id(0));
+ }
+
+ #[test]
+ fn test_merging() {
+ let mut dag = {
+ let head = test::Node {
+ parents: vec![],
+ data: 0,
+ };
+ let b = test::Backend::new(vec![Some(head.clone())]);
+ let dag = tokio_test::block_on(AsyncDag::new(b, head));
+ assert!(dag.is_ok());
+ dag.unwrap()
+ };
+
+ let mut branched = dag.branch();
+
+ {
+ assert_eq!(dag.backend.0.read().unwrap().len(), 1);
+ assert_eq!(dag.head, test::Id(0));
+ let new_head = test::Node {
+ parents: vec![test::Id(0)],
+ data: 1,
+ };
+
+ let id = tokio_test::block_on(dag.update_head(new_head));
+ assert!(id.is_ok());
+ let _ = id.unwrap();
+
+ assert_eq!(dag.backend.0.read().unwrap().len(), 2);
+ assert_eq!(dag.head, test::Id(1));
+ }
+
+ {
+ assert_eq!(branched.backend.0.read().unwrap().len(), 2);
+ assert_eq!(branched.head, test::Id(0));
+ let new_head = test::Node {
+ parents: vec![test::Id(0)],
+ data: 2,
+ };
+
+ let id = tokio_test::block_on(branched.update_head(new_head));
+ assert!(id.is_ok());
+ let _ = id.unwrap();
+
+ assert_eq!(branched.backend.0.read().unwrap().len(), 3);
+ assert_eq!(branched.head, test::Id(2));
+ }
+
+ struct M;
+ impl super::Merger<test::Id, test::Node> for M {
+ type Error = TestError;
+
+ fn create_merge_node(
+ &self,
+ left_id: &test::Id,
+ right_id: &test::Id,
+ ) -> Result<test::Node, Self::Error> {
+ Ok(test::Node {
+ parents: vec![*left_id, *right_id],
+ data: 3,
+ })
+ }
+ }
+
+ let merge = tokio_test::block_on(dag.merge(&branched, M));
+ assert!(merge.is_ok());
+ let _ = merge.unwrap();
+
+ assert_eq!(dag.backend.0.read().unwrap().len(), 4);
+ assert_eq!(dag.head, test::Id(3));
+ }
+
+ #[test]
+ fn test_merging_merge_fn() {
+ let mut dag = {
+ let head = test::Node {
+ parents: vec![],
+ data: 0,
+ };
+ let b = test::Backend::new(vec![Some(head.clone())]);
+ let dag = tokio_test::block_on(AsyncDag::new(b, head));
+ assert!(dag.is_ok());
+ dag.unwrap()
+ };
+
+ let mut branched = dag.branch();
+
+ {
+ assert_eq!(dag.backend.0.read().unwrap().len(), 1);
+ assert_eq!(dag.head, test::Id(0));
+ let new_head = test::Node {
+ parents: vec![test::Id(0)],
+ data: 1,
+ };
+
+ let id = tokio_test::block_on(dag.update_head(new_head));
+ assert!(id.is_ok());
+ let _ = id.unwrap();
+
+ assert_eq!(dag.backend.0.read().unwrap().len(), 2);
+ assert_eq!(dag.head, test::Id(1));
+ }
+
+ {
+ assert_eq!(branched.backend.0.read().unwrap().len(), 2);
+ assert_eq!(branched.head, test::Id(0));
+ let new_head = test::Node {
+ parents: vec![test::Id(0)],
+ data: 2,
+ };
+
+ let id = tokio_test::block_on(branched.update_head(new_head));
+ assert!(id.is_ok());
+ let _ = id.unwrap();
+
+ assert_eq!(branched.backend.0.read().unwrap().len(), 3);
+ assert_eq!(branched.head, test::Id(2));
+ }
+
+ let merge = tokio_test::block_on(dag.merge(
+ &branched,
+ |left_id: &test::Id, right_id: &test::Id| {
+ Ok(test::Node {
+ parents: vec![*left_id, *right_id],
+ data: 3,
+ })
+ },
+ ));
+ assert!(merge.is_ok());
+ let _ = merge.unwrap();
+
+ assert_eq!(dag.backend.0.read().unwrap().len(), 4);
+ assert_eq!(dag.head, test::Id(3));
+ }
+}
diff --git a/src/dag_backend.rs b/src/dag_backend.rs
new file mode 100644
index 0000000..09dc428
--- /dev/null
+++ b/src/dag_backend.rs
@@ -0,0 +1,106 @@
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+//
+
+use async_trait::async_trait;
+
+use crate::Node;
+use crate::NodeId;
+
+/// An interface to a DAG backend storage
+///
+/// A DAG backend storage is nothing more than a thing that can store (`DagBackend::put`) and load
+/// (`DagBackend::get`) nodes.
+#[async_trait]
+pub trait DagBackend<Id, N>
+where
+ N: Node,
+ Id: NodeId + Send,
+{
+ type Error;
+
+ /// Get a `Node` from the backend that is identified by `id`
+ ///
+ /// # Returns
+ ///
+ /// * Should return Err(_) if the operation failed.
+ /// * Should return Ok(None) if there is no node that is identified by `id`
+ /// * Otherwise return the Id along with the node identified by it
+ async fn get(&self, id: Id) -> Result<Option<(Id, N)>, Self::Error>;
+
+ /// Store a `node` in the backend, returning its `Id`
+ ///
+ /// This function should store the `node` in the backend and return the `id` the node has.
+ async fn put(&mut self, node: N) -> Result<Id, Self::Error>;
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::test_impl as test;
+ use crate::*;
+
+ #[test]
+ fn test_backend_get() {
+ let b = test::Backend::new(vec![Some(test::Node {
+ parents: vec![],
+ data: 0,
+ })]);
+
+ let node = tokio_test::block_on(b.get(test::Id(0)));
+ assert!(node.is_ok());
+ let node = node.unwrap();
+
+ assert!(node.is_some());
+ let node = node.unwrap();
+
+ assert_eq!(node.0, test::Id(0));
+ assert!(node.1.parents.is_empty());
+ }
+
+ #[test]
+ fn test_backend_put() {
+ let mut b = test::Backend::new(vec![Some(test::Node {
+ parents: vec![],
+ data: 0,
+ })]);
+
+ let _ = tokio_test::block_on(b.put({
+ test::Node {
+ parents: vec![],
+ data: 1,
+ }
+ }));
+
+ {
+ let node = tokio_test::block_on(b.get(test::Id(0)));
+ assert!(node.is_ok());
+ let node = node.unwrap();
+
+ assert!(node.is_some());
+ let node = node.unwrap();
+
+ assert_eq!(node.0, test::Id(0));
+ assert!(node.1.parents.is_empty());
+ }
+ {
+ let node = tokio_test::block_on(b.get(test::Id(1)));
+ assert!(node.is_ok());
+ let node = node.unwrap();
+
+ assert!(node.is_some());
+ let node = node.unwrap();
+
+ assert_eq!(node.0, test::Id(1));
+ assert!(node.1.parents.is_empty());
+ }
+ {
+ let node = tokio_test::block_on(b.get(test::Id(2)));
+ assert!(node.is_ok());
+ let node = node.unwrap();
+
+ assert!(node.is_none());
+ }
+ }
+}
diff --git a/src/error.rs b/src/error.rs
new file mode 100644
index 0000000..fd91045
--- /dev/null
+++ b/src/error.rs
@@ -0,0 +1,11 @@
+#[derive(Debug, thiserror::Error)]
+pub enum Error<Id>
+where
+ Id: crate::NodeId,
+{
+ #[error("Node not found: {:?}", .0)]
+ NodeNotFound(Id),
+
+ #[error("Node does not have HEAD as parent")]
+ HeadNotAParent,
+}
diff --git a/src/id.rs b/src/id.rs
deleted file mode 100644
index 018ae60..0000000
--- a/src/id.rs
+++ /dev/null
@@ -1,18 +0,0 @@
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-//
-
-use std::fmt::Debug;
-
-/// An "ID" is an object that can be used to uniquely identify a node in the DAG.
-///
-/// In git-speak, this would be a SHA1 hash.
-///
-pub trait Id : Clone + Debug + PartialEq + Eq {
-
- /* empty */
-
-}
-
diff --git a/src/lib.rs b/src/lib.rs
index bda0996..67bce67 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,6 +4,20 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
-pub mod id;
-pub mod node;
-pub mod repository;
+mod async_dag;
+pub use async_dag::*;
+
+mod dag_backend;
+pub use dag_backend::*;
+
+mod node;
+pub use node::*;
+
+mod node_id;
+pub use node_id::*;
+
+mod error;
+pub use error::*;
+
+#[cfg(test)]
+mod test_impl;
diff --git a/src/node.rs b/src/node.rs
index b6a088b..58515a8 100644
--- a/src/node.rs
+++ b/src/node.rs
@@ -4,36 +4,10 @@
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
//
-use std::fmt::Debug;
+use crate::NodeId;
-use crate::id::Id;
-
-use futures::future::Future;
-use futures::stream::Stream;
-
-/// A "Node" is an object of the (DA)Graph
-///
-/// In git-speak, this would be an "Object".
-///
-///
-/// # Equality
-///
-/// A node might be compared to another node. In git, for example, equality would be the equality
-/// of the nodes ids, because objects are non-mutable.
-///
-pub trait Node: Debug + PartialEq + Eq {
- type Id: Id;
- type NodePayload: Debug;
- type Error: Debug;
- type Payload: Future<Item = Self::NodePayload, Error = Self::Error>;
-
- /// It should be trivial to get the Id of a Node.
- fn id(&self) -> Self::Id;
-
- /// Fetch the payload of the node.
- fn payload(&self) -> Self::Payload;
-
- /// Fetch the Ids of the parents of this node
- fn parent_ids(&self) -> Stream<Item = Self::Id, Error = Self::Error>;
+/// A Node in the DAG, holding the data.
+pub trait Node {
+ type Id: NodeId;
+ fn parent_ids(&self) -> Vec<Self::Id>;
}
-
diff --git a/src/node_id.rs b/src/node_id.rs
new file mode 100644
index 0000000..5d0663a
--- /dev/null
+++ b/src/node_id.rs
@@ -0,0 +1,10 @@
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+//
+
+/// A unique identifier for a `Node`
+///
+/// The `NodeId` should be cheap to clone (for example a UUID or some form of a hash value).
+pub trait NodeId: Clone + Eq + PartialEq + std::fmt::Debug {}
diff --git a/src/repository.rs b/src/repository.rs
deleted file mode 100644
index 57f2d63..0000000
--- a/src/repository.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-//
-// This Source Code Form is subject to the terms of the Mozilla Public
-// License, v. 2.0. If a copy of the MPL was not distributed with this
-// file, You can obtain one at http://mozilla.org/MPL/2.0/.
-//
-
-use std::fmt::Debug;
-
-use crate::node;
-use crate::id;
-
-use futures::future::Future;
-
-///
-pub trait Repository: Debug {
- type Id: id::Id;
- type Error: Debug;
- type Node: node::Node<Id = Self::Id, Error = Self::Error>;
-
- type Get: Future<Item = Self::Node, Error = Self::Error>;
-
- /// It should be trivial to get the Id of a Node.
- fn get<ID>(id: ID) -> Result<Self::Get, Self::Error>
- where ID: id::Id;
-}
-
diff --git a/src/test_impl.rs b/src/test_impl.rs
new file mode 100644
index 0000000..c8f2b51
--- /dev/null
+++ b/src/test_impl.rs
@@ -0,0 +1,79 @@
+//
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+//
+
+use std::sync::Arc;
+use std::sync::RwLock;
+
+use async_trait::async_trait;
+
+#[derive(Debug)]
+pub struct TestError;
+
+impl<Id> From<crate::Error<Id>> for TestError
+where
+ Id: crate::NodeId,
+{
+ fn from(_ce: crate::Error<Id>) -> Self {
+ TestError
+ }
+}
+
+#[derive(Copy, Clone, Eq, PartialEq, std::hash::Hash, Debug)]
+pub struct Id(pub(crate) usize);
+
+impl crate::NodeId for Id {}
+
+#[derive(Clone, Debug)]
+pub struct Node {
+ pub(crate) parents: Vec<Id>,
+ // data the node holds, used to create the ID in tests as "hashing" for unique id
+ pub(crate) data: usize,
+}
+
+impl crate::Node for Node {
+ type Id = Id;
+
+ fn parent_ids(&self) -> Vec<Self::Id> {
+ self.parents.clone()
+ }
+}
+
+/// The backend for the tests
+///
+/// This is `Clone` because we can test branching only with a clonable backend.
+/// A real backend would not implement the storage itself, but rather a way to retrieve the data
+/// from some storage mechansim (think IPFS), and thus `Clone`ing a backend is nothing esotheric.
+#[derive(Clone, Debug)]
+pub struct Backend(pub(crate) Arc<RwLock<Vec<Option<Node>>>>);
+
+impl Backend {
+ pub fn new(v: Vec<Option<Node>>) -> Self {
+ Backend(Arc::new(RwLock::new(v)))
+ }
+}
+
+#[async_trait]
+impl crate::DagBackend<Id, Node> for Backend {
+ type Error = TestError;
+
+ async fn get(&self, id: Id) -> Result<Option<(Id, Node)>, Self::Error> {
+ if self.0.read().unwrap().len() < id.0 + 1 {
+ Ok(None)
+ } else {
+ Ok(self.0.read().unwrap()[id.0].clone().map(|node| (id, node)))
+ }
+ }
+
+ async fn put(&mut self, node: Node) -> Result<Id, Self::Error> {
+ while self.0.read().unwrap().len() < node.data + 1 {
+ self.0.write().unwrap().push(None)
+ }
+
+ let idx = node.data;
+ self.0.write().unwrap()[idx] = Some(node);
+ Ok(Id(idx))
+ }
+}