use super::path::HdkPathExt;
use crate::prelude::*;
use hdi::hash_path::{
anchor::{Anchor, ROOT},
path::{Component, Path},
};
use holochain_zome_types::entry::GetStrategy;
pub trait AnchorExt {
fn to_typed_path<T, E>(&self, link_type: T) -> Result<TypedPath, WasmError>
where
ScopedLinkType: TryFrom<T, Error = E>,
WasmError: From<E>;
}
impl AnchorExt for Anchor {
fn to_typed_path<T, E>(&self, link_type: T) -> Result<TypedPath, WasmError>
where
ScopedLinkType: TryFrom<T, Error = E>,
WasmError: From<E>,
{
let path: Path = self.into();
let typed_path = path.typed(link_type)?;
Ok(typed_path.with_strategy(self.strategy))
}
}
pub trait TryFromPath {
fn try_from_path(path: &Path) -> Result<Anchor, WasmError>;
}
impl TryFromPath for Anchor {
fn try_from_path(path: &Path) -> Result<Self, WasmError> {
let components: Vec<Component> = path.as_ref().to_owned();
if components.len() == 2 || components.len() == 3 {
if components[0] == Component::new(ROOT.to_vec()) {
Ok(Anchor::new(
std::str::from_utf8(components[1].as_ref())
.map_err(|e| wasm_error!(SerializedBytesError::Deserialize(e.to_string())))?
.to_string(),
match components.get(2) {
Some(component) => Some(
std::str::from_utf8(component.as_ref())
.map_err(|e| {
wasm_error!(SerializedBytesError::Deserialize(e.to_string()))
})?
.to_string(),
),
None => None,
},
))
} else {
Err(wasm_error!(WasmErrorInner::Serialize(
SerializedBytesError::Deserialize(format!(
"Bad anchor path root {:0?} should be {:1?}",
components[0].as_ref(),
ROOT,
),)
)))
}
} else {
Err(wasm_error!(WasmErrorInner::Serialize(
SerializedBytesError::Deserialize(format!(
"Bad anchor path length {}",
components.len()
),)
)))
}
}
}
pub fn anchor<T, E>(
link_type: T,
anchor_type: String,
anchor_text: String,
) -> ExternResult<holo_hash::EntryHash>
where
ScopedLinkType: TryFrom<T, Error = E>,
WasmError: From<E>,
{
anchor_with_strategy(link_type, anchor_type, anchor_text, GetStrategy::default())
}
pub fn anchor_with_strategy<T, E>(
link_type: T,
anchor_type: String,
anchor_text: String,
strategy: GetStrategy,
) -> ExternResult<holo_hash::EntryHash>
where
ScopedLinkType: TryFrom<T, Error = E>,
WasmError: From<E>,
{
let anchor = Anchor::new(anchor_type, Some(anchor_text)).with_strategy(strategy);
let path = anchor.to_typed_path(link_type)?;
path.ensure()?;
path.path_entry_hash()
}
pub fn list_anchor_type_addresses<T, E>(link_type: T) -> ExternResult<Vec<AnyLinkableHash>>
where
ScopedLinkType: TryFrom<T, Error = E>,
WasmError: From<E>,
{
list_anchor_type_addresses_with_strategy(link_type, GetStrategy::default())
}
pub fn list_anchor_type_addresses_with_strategy<T, E>(
link_type: T,
strategy: GetStrategy,
) -> ExternResult<Vec<AnyLinkableHash>>
where
ScopedLinkType: TryFrom<T, Error = E>,
WasmError: From<E>,
{
let links = Path::from(vec![Component::new(ROOT.to_vec())])
.typed(link_type)?
.with_strategy(strategy)
.children()?
.into_iter()
.map(|link| link.target)
.collect();
Ok(links)
}
pub fn list_anchor_addresses<T, E>(
link_type: T,
anchor_type: String,
) -> ExternResult<Vec<AnyLinkableHash>>
where
ScopedLinkType: TryFrom<T, Error = E>,
WasmError: From<E>,
{
list_anchor_addresses_with_strategy(link_type, anchor_type, GetStrategy::default())
}
pub fn list_anchor_addresses_with_strategy<T, E>(
link_type: T,
anchor_type: String,
strategy: GetStrategy,
) -> ExternResult<Vec<AnyLinkableHash>>
where
ScopedLinkType: TryFrom<T, Error = E>,
WasmError: From<E>,
{
let anchor = Anchor::new(anchor_type, None).with_strategy(strategy);
let links = anchor
.to_typed_path(link_type)?
.children()?
.into_iter()
.map(|link| link.target)
.collect();
Ok(links)
}
pub fn list_anchor_tags<T, E>(link_type: T, anchor_type: String) -> ExternResult<Vec<String>>
where
ScopedLinkType: TryFrom<T, Error = E>,
WasmError: From<E>,
{
list_anchor_tags_with_strategy(link_type, anchor_type, GetStrategy::default())
}
pub fn list_anchor_tags_with_strategy<T, E>(
link_type: T,
anchor_type: String,
strategy: GetStrategy,
) -> ExternResult<Vec<String>>
where
ScopedLinkType: TryFrom<T, Error = E>,
WasmError: From<E>,
{
let anchor = Anchor::new(anchor_type, None).with_strategy(strategy);
let path = anchor.to_typed_path(link_type)?;
path.ensure()?;
let hopefully_anchor_tags: Result<Vec<String>, WasmError> = path
.children_paths()?
.into_iter()
.map(|path| match Anchor::try_from_path(&path.path) {
Ok(anchor) => match anchor.anchor_text {
Some(text) => Ok(text),
None => Err(wasm_error!(WasmErrorInner::Serialize(
SerializedBytesError::Deserialize("missing anchor text".into(),)
))),
},
Err(e) => Err(e),
})
.collect();
let mut anchor_tags = hopefully_anchor_tags?;
anchor_tags.sort();
anchor_tags.dedup();
Ok(anchor_tags)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn hash_path_anchor_from_path() {
let path = Path::from(vec![
Component::from(vec![0, 0]),
Component::from(vec![102, 111, 111]),
Component::from(vec![98, 97, 114]),
]);
assert_eq!(
Anchor::try_from_path(&path).unwrap(),
Anchor::new("foo".into(), Some("bar".into())),
);
}
#[test]
fn test_anchor_ext_preserves_strategy() {
#[derive(Clone, Copy, Debug)]
struct TestLinkType;
impl TryFrom<TestLinkType> for ScopedLinkType {
type Error = WasmError;
fn try_from(_: TestLinkType) -> Result<Self, Self::Error> {
Ok(ScopedLinkType {
zome_index: 0.into(),
zome_type: 0.into(),
})
}
}
let anchor = Anchor::new("test_type".to_string(), Some("test_text".to_string()))
.with_strategy(GetStrategy::Local);
let typed_path = anchor
.to_typed_path(TestLinkType)
.expect("Should convert to TypedPath");
assert_eq!(typed_path.strategy, GetStrategy::Local);
}
}