hdk 0.7.0-dev.15

The Holochain HDK
Documentation
use crate::prelude::*;
use hdi::hash_path::path::{root_hash, Component, TypedPath};

pub trait HdkPathExt {
    fn children(&self) -> ExternResult<Vec<holochain_zome_types::link::Link>>;
    fn children_paths(&self) -> ExternResult<Vec<TypedPath>>;
    fn children_details(&self) -> ExternResult<holochain_zome_types::link::LinkDetails>;
    fn ensure(&self) -> ExternResult<()>;
    fn exists(&self) -> ExternResult<bool>;
}

impl HdkPathExt for TypedPath {
    /// Touch and list all the links from this path to paths below it.
    /// Only returns links between paths, not to other entries that might have their own links.
    fn children(&self) -> ExternResult<Vec<holochain_zome_types::link::Link>> {
        Self::ensure(self)?;

        let mut unwrapped = get_links(
            LinkQuery::new(
                self.path_entry_hash()?,
                LinkTypeFilter::single_type(self.link_type.zome_index, self.link_type.zome_type),
            ),
            self.strategy,
        )?;

        // Only need one of each hash to build the tree.
        unwrapped.sort_unstable_by(|a, b| a.tag.cmp(&b.tag));
        unwrapped.dedup_by(|a, b| a.tag.eq(&b.tag));
        Ok(unwrapped)
    }

    /// Touch and list all the links from this path to paths below it.
    /// Same as `Path::children` but returns `Vec<Path>` rather than `Vec<Link>`.
    /// This is more than just a convenience. In general it's not possible to
    /// construct a full `Path` from a child `Link` alone as only a single
    /// `Component` is encoded into the link tag. To build a full child path
    /// the parent path + child link must be combined, which this function does
    /// to produce each child, by using `&self` as that parent.
    fn children_paths(&self) -> ExternResult<Vec<TypedPath>> {
        let children = self.children()?;
        let components: ExternResult<Vec<Option<Component>>> = children
            .into_iter()
            .map(|link| {
                let component_bytes = &link.tag.0[..];
                if component_bytes.is_empty() {
                    Ok(None)
                } else {
                    Ok(Some(
                        SerializedBytes::from(UnsafeBytes::from(component_bytes.to_vec()))
                            .try_into()
                            .map_err(|e: SerializedBytesError| wasm_error!(e))?,
                    ))
                }
            })
            .collect();
        Ok(components?
            .into_iter()
            .map(|maybe_component| {
                let mut new_path = self.path.clone();
                if let Some(component) = maybe_component {
                    new_path.append_component(component);
                }
                new_path.into_typed(self.link_type)
            })
            .collect())
    }

    fn children_details(&self) -> ExternResult<holochain_zome_types::link::LinkDetails> {
        Self::ensure(self)?;
        get_links_details(
            LinkQuery::new(
                self.path_entry_hash()?,
                LinkTypeFilter::single_type(self.link_type.zome_index, self.link_type.zome_type),
            )
            .tag_prefix(holochain_zome_types::link::LinkTag::new([])),
            self.strategy,
        )
    }

    /// Ensures that this path exists by recursively creating missing parent links.
    ///
    /// This function checks whether the current path already exists.
    /// If it does not, it recursively ensures that all parent paths exist and then
    /// creates the appropriate link for this path.
    ///
    /// The behavior depends on whether the path is the root:
    ///
    /// - If the path is the root and does not exist, a link is created from the
    ///   global root hash to this path entry.
    /// - If the path is not the root, its parent is first ensured recursively by calling [`Self::ensure`] on it,
    ///   and then a link is created from the parent path entry to this path entry.
    ///
    /// If the path already exists, this function is a no-op.
    ///
    /// # Errors
    ///
    /// Returns an error if:
    /// - Checking for existence fails
    /// - [`Self::ensure`] on a parent path fails
    /// - Creating any required link fails
    ///
    /// # Notes
    ///
    /// This function does **not** create entries; it only creates links at
    /// deterministic path hashes.
    ///
    /// `Path` operates on so-called *ghost entries*: no entry is ever written to
    /// the DHT for a path itself. Instead, the hash that *would* correspond to a
    /// path entry is deterministically derived and used as the base or target
    /// address for links.
    ///
    /// In other words, [`Path::path_entry_hash`] does not require a prior
    /// entry create; it computes a stable hash that is used purely as a link
    /// anchor in the DHT.
    ///
    /// The operation is idempotent: calling [`Self::ensure`] multiple times for the same
    /// path will not create duplicate links, given that [`Self::exists`] correctly
    /// reflects the current state of the DHT.
    fn ensure(&self) -> ExternResult<()> {
        if !self.exists()? {
            if self.is_root() {
                create_link(
                    root_hash()?,
                    self.path_entry_hash()?,
                    self.link_type,
                    self.make_tag()?,
                )?;
            } else if let Some(parent) = self.parent() {
                parent.ensure()?;
                create_link(
                    parent.path_entry_hash()?,
                    self.path_entry_hash()?,
                    self.link_type,
                    self.make_tag()?,
                )?;
            }
        }
        Ok(())
    }

    /// Does data exist at the hash we expect?
    fn exists(&self) -> ExternResult<bool> {
        if self.0.is_empty() {
            Ok(false)
        } else if self.is_root() {
            let this_paths_hash: AnyLinkableHash = self.path_entry_hash()?.into();
            let exists = get_links(
                LinkQuery::new(
                    root_hash()?,
                    LinkTypeFilter::single_type(
                        self.link_type.zome_index,
                        self.link_type.zome_type,
                    ),
                )
                .tag_prefix(self.make_tag()?),
                self.strategy,
            )?
            .iter()
            .any(|Link { target, .. }| *target == this_paths_hash);
            Ok(exists)
        } else {
            let parent = self
                .parent()
                .expect("Must have parent if not empty or root");
            let this_paths_hash: AnyLinkableHash = self.path_entry_hash()?.into();
            let exists = get_links(
                LinkQuery::new(
                    parent.path_entry_hash()?,
                    LinkTypeFilter::single_type(
                        self.link_type.zome_index,
                        self.link_type.zome_type,
                    ),
                )
                .tag_prefix(self.make_tag()?),
                self.strategy,
            )?
            .iter()
            .any(|Link { target, .. }| *target == this_paths_hash);
            Ok(exists)
        }
    }
}