#![deny(missing_docs)]
#![deny(broken_intra_doc_links)]
#![deny(unsafe_code)]
use assemblage_kv::{self, storage::Storage, KvStore, Snapshot};
use async_recursion::async_recursion;
use broadcast::BroadcastId;
use data::{BlockStyle, Child, Id, Layout, Node, Parent, SpanStyle, Styles};
use std::collections::{BTreeSet, HashSet};
pub mod broadcast;
mod core;
pub mod data;
mod index;
enum Slot {
Node = 0,
Parents = 1,
Grams = 2,
Count = 3,
Overlaps = 4,
BroadcastPublished = 5,
BroadcastSubscribed = 6,
}
#[derive(Debug)]
pub enum Error {
StoreError {
err: assemblage_kv::Error,
operation: String,
context: String,
},
NodeError(data::Error),
IdNotFound {
id: Id,
operation: String,
context: String,
},
BroadcastIdNotFound(BroadcastId),
InvalidBroadcastUrl {
url: String,
err: String,
},
InvalidBroadcastResponse {
url: String,
err: String,
},
}
trait AsDbErrorWithContext<T> {
fn with_context(self, op: &str, context: &str) -> Result<T>;
}
impl<T> AsDbErrorWithContext<T> for std::result::Result<T, assemblage_kv::Error> {
fn with_context(self, op: &str, context: &str) -> Result<T> {
self.map_err(|err| Error::StoreError {
err,
operation: op.to_string(),
context: context.to_string(),
})
}
}
impl<T> AsDbErrorWithContext<T> for std::result::Result<T, assemblage_kv::storage::Error> {
fn with_context(self, op: &str, context: &str) -> Result<T> {
self.map_err(assemblage_kv::Error::from)
.with_context(op, context)
}
}
trait AsIdNotFoundErrorWithContext<T> {
fn ok_or_invalid(self, id: Id, op: &str, context: &str) -> Result<T>;
}
impl<T> AsIdNotFoundErrorWithContext<T> for std::result::Result<Option<T>, Error> {
fn ok_or_invalid(self, id: Id, op: &str, context: &str) -> Result<T> {
match self {
Ok(Some(n)) => Ok(n),
Ok(None) => Err(Error::IdNotFound {
id,
operation: op.to_string(),
context: context.to_string(),
}),
Err(e) => Err(e),
}
}
}
impl<T> AsIdNotFoundErrorWithContext<T> for std::result::Result<Option<T>, assemblage_kv::Error> {
fn ok_or_invalid(self, id: Id, op: &str, context: &str) -> Result<T> {
self.with_context(op, context)
.ok_or_invalid(id, op, context)
}
}
impl<E: Into<assemblage_kv::Error>> From<E> for Error {
fn from(e: E) -> Self {
Error::StoreError {
err: e.into(),
operation: "".to_string(),
context: "".to_string(),
}
}
}
impl From<data::Error> for Error {
fn from(e: data::Error) -> Self {
Error::NodeError(e)
}
}
#[cfg(target_arch = "wasm32")]
impl From<Error> for wasm_bindgen::JsValue {
fn from(e: Error) -> Self {
wasm_bindgen::JsValue::from_str(&format!("{:?}", e))
}
}
pub type Result<R> = std::result::Result<R, Error>;
pub struct Db<S: Storage> {
store: KvStore<S>,
}
pub struct DbSnapshot<'a, S: Storage> {
pub(crate) store: Snapshot<'a, S>,
}
#[derive(Debug, Clone)]
pub enum RestoredNode {
Restored(Node),
NoNeedToRestoreNode,
}
#[derive(Debug, Clone)]
pub enum PreviewedNode {
Block(Id, Node),
Empty,
Cyclic,
}
impl<S: Storage> DbSnapshot<'_, S> {
pub async fn is_span(&self, node: &Node) -> Result<bool> {
self.is_span_recur(node).await
}
#[async_recursion(?Send)]
async fn is_span_recur(&self, node: &Node) -> Result<bool> {
Ok(match node {
Node::Text(_) => true,
Node::List(layout, _) => *layout == Layout::Chain,
Node::Styled(styles, child) => match styles {
Styles::Block(_) => false,
Styles::Span(_) => match child.as_ref() {
Child::Lazy(id) => {
let child =
self.get(*id)
.await
.ok_or_invalid(*id, "is_span", "get lazy child")?;
self.is_span_recur(&child).await?
}
Child::Eager(node) => self.is_span_recur(node).await?,
},
},
})
}
pub async fn is_block(&self, node: &Node) -> Result<bool> {
Ok(!self.is_span(node).await?)
}
pub async fn is_link(&self, child: &Node, parent: &Node) -> Result<bool> {
Ok(self.is_block(child).await? && self.is_span(parent).await?)
}
pub async fn is_blank(&self, id: Id) -> Result<bool> {
let mut visited = HashSet::new();
let mut candidates = vec![id];
while let Some(id) = candidates.pop() {
if visited.contains(&id) {
continue;
}
visited.insert(id);
let node = self
.get(id)
.await
.ok_or_invalid(id, "is_blank", "get node")?;
match &node {
Node::Text(l) => {
if !l.is_blank() {
return Ok(false);
}
}
Node::List(_, children) => {
for child in children {
candidates.push(child.id()?);
}
}
Node::Styled(_, child) => candidates.push(child.id()?),
}
}
Ok(true)
}
pub async fn is_cyclic(&self, id: Id) -> Result<bool> {
let mut visited = HashSet::new();
let mut candidates = vec![id];
while let Some(id) = candidates.pop() {
if visited.contains(&id) {
return Ok(true);
}
visited.insert(id);
let (_, children) = self
.get(id)
.await
.ok_or_invalid(id, "is_cyclic", "get node")?
.split();
for child in children {
candidates.push(child.id()?);
}
}
Ok(false)
}
pub async fn preview(&self, mut id: Id) -> Result<PreviewedNode> {
let mut block_styles: BTreeSet<BlockStyle> = BTreeSet::new();
let mut span_styles: BTreeSet<SpanStyle> = BTreeSet::new();
let mut visited = HashSet::new();
while !visited.contains(&id) {
visited.insert(id);
let mut node = self
.get(id)
.await
.ok_or_invalid(id, "preview", "get node")?;
match &node {
Node::Text(l) => {
return Ok(if l.is_blank() {
PreviewedNode::Empty
} else {
node = Node::styled(span_styles, node);
node = Node::styled(block_styles, node);
PreviewedNode::Block(id, node)
});
}
Node::List(_, children) if children.is_empty() => {
return Ok(PreviewedNode::Empty);
}
Node::List(Layout::Chain, _) => {
return Ok(if self.is_blank(id).await? {
PreviewedNode::Empty
} else if self.is_cyclic(id).await? {
PreviewedNode::Cyclic
} else {
node = Node::styled(span_styles, node);
node = Node::styled(block_styles, node);
PreviewedNode::Block(id, node)
})
}
Node::List(_, children) => {
id = children[0].id()?;
}
Node::Styled(s, child) => {
match s {
Styles::Block(s) => block_styles.extend(s),
Styles::Span(s) => span_styles.extend(s),
};
id = (*child).id()?;
}
};
}
Ok(PreviewedNode::Cyclic)
}
pub async fn ancestor_path(&self, id: Id) -> Result<Vec<Parent>> {
let stop_at_link = false;
self.ancestor_path_until(id, stop_at_link).await
}
pub async fn ancestor_path_until_link(&self, id: Id) -> Result<Vec<Parent>> {
let stop_at_link = true;
self.ancestor_path_until(id, stop_at_link).await
}
async fn ancestor_path_until(&self, mut id: Id, stop_at_link: bool) -> Result<Vec<Parent>> {
let mut path = Vec::new();
Ok(loop {
let parents = self.parents(id).await?;
if parents.len() != 1 {
break path.into_iter().rev().collect();
} else {
let parent = parents.iter().next().unwrap();
let is_cyclic = path.iter().any(|p| p == parent);
let is_link = if stop_at_link {
let child =
self.get(id)
.await
.ok_or_invalid(id, "ancestor_path_until", "get child")?;
let parent = self.get(parent.id).await.ok_or_invalid(
parent.id,
"ancestor_path_until",
"get parent",
)?;
self.is_link(&child, &parent).await?
} else {
false
};
if is_cyclic || is_link {
break path.into_iter().rev().collect();
} else {
let parent = parents.into_iter().next().unwrap();
id = parent.id;
path.push(parent);
}
}
})
}
pub async fn descendants(&self, id: Id) -> Result<HashSet<Id>> {
let stop_at_link = false;
self.descendants_until(id, stop_at_link).await
}
pub async fn descendants_until_links(&self, id: Id) -> Result<HashSet<Id>> {
let stop_at_link = true;
self.descendants_until(id, stop_at_link).await
}
async fn descendants_until(&self, id: Id, stop_at_link: bool) -> Result<HashSet<Id>> {
let node = self
.get(id)
.await
.ok_or_invalid(id, "descendants_until", "get main node")?;
let mut candidates = vec![(node, id)];
let mut descendants = HashSet::new();
while let Some((node, id)) = candidates.pop() {
if descendants.contains(&id) {
continue;
}
descendants.insert(id);
for child in node.children() {
let id = child.id()?;
let child_node =
self.get(id)
.await
.ok_or_invalid(id, "descendants_until", "get child")?;
if stop_at_link && self.is_link(&child_node, &node).await? {
descendants.insert(id);
} else {
candidates.push((child_node, id));
}
}
}
Ok(descendants)
}
pub async fn has_shared_descendants_until_links(&self, id: Id) -> Result<bool> {
let node = self
.get(id)
.await
.ok_or_invalid(id, "descendants_until", "get main node")?;
let mut candidates = vec![(node, id)];
let mut descendants = HashSet::new();
while let Some((node, id)) = candidates.pop() {
if descendants.contains(&id) {
continue;
}
descendants.insert(id);
for child in node.children() {
let id = child.id()?;
let child_node =
self.get(id)
.await
.ok_or_invalid(id, "descendants_until", "get child")?;
let parents = self.parents(id).await?;
if parents.len() > 1 {
return Ok(true);
}
candidates.push((child_node, id));
}
}
Ok(false)
}
}
impl<S: Storage> DbSnapshot<'_, S> {
pub async fn update<F>(&mut self, id: Id, f: F) -> Result<()>
where
F: FnOnce(&mut Vec<Child>),
{
let node = self.get(id).await.ok_or_invalid(id, "update", "get node")?;
match node {
Node::List(layout, mut children) => {
f(&mut children);
self.swap(id, Node::List(layout, children)).await
}
_ => Err(Error::NodeError(data::Error::WrongNodeType {
expected: String::from("List"),
actual: node,
})),
}
}
pub async fn remove(&mut self, id: Id, index: u32) -> Result<()> {
self.update(id, |elements| {
elements.remove(index as usize);
})
.await
}
pub async fn replace<C: Into<Child>>(&mut self, id: Id, index: u32, child: C) -> Result<()> {
self.update(id, |elements| {
elements[index as usize] = child.into();
})
.await
}
pub async fn insert<C: Into<Child>>(&mut self, id: Id, index: u32, child: C) -> Result<()> {
self.update(id, |elements| {
elements.insert(index as usize, child.into());
})
.await
}
pub async fn push<C: Into<Child>>(&mut self, id: Id, child: C) -> Result<()> {
self.update(id, |elements| {
elements.push(child.into());
})
.await
}
}
impl Child {
pub async fn of<'a, S: Storage + 'a>(
&'a self,
db: impl Into<Option<&'a DbSnapshot<'a, S>>>,
) -> Result<Node> {
match (self, db.into()) {
(Child::Eager(node), _) => Ok(node.clone()),
(Child::Lazy(id), Some(db)) => {
db.get(*id)
.await
.ok_or_invalid(*id, "of", "get lazy child of node")
}
(Child::Lazy(id), None) => Err(Error::IdNotFound {
id: *id,
operation: "of".to_string(),
context: "get eager child of node".to_string(),
}),
}
}
}
#[macro_export]
macro_rules! tx {
(|$db:ident| $tx:expr) => {{
use assemblage_db::{Db, DbSnapshot};
let mut $db = $db.current().await;
let ret = $tx;
$db.commit().await?;
ret
}};
(|$db:ident| -> Result<$r:ty, $e:ty> $tx:block) => {{
use assemblage_db::{Db, DbSnapshot};
let mut $db = $db.current().await;
let ret = $tx;
$db.commit().await?;
ret
}};
}