1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243
use crate::hash_path::path::Component;
use crate::hash_path::path::Path;
use crate::prelude::*;
use holochain_wasmer_guest::*;
/// This is the root of the [ `Path` ] tree.
///
/// Forms the entry point to all anchors so that agents can navigate down the tree from here.
pub const ROOT: &[u8; 2] = &[0x00, 0x00];
#[derive(PartialEq, SerializedBytes, serde::Serialize, serde::Deserialize, Debug, Clone)]
/// An anchor can only be 1 or 2 levels deep as "type" and "text".
///
/// The second level is optional and the Strings use the standard [ `TryInto` ] for path [ `Component` ] internally.
///
/// __Anchors are required to be included in an application's [ `entry_defs` ]__ callback and so implement all the standard methods.
/// Technically the [ `Anchor` ] entry definition is the [ `Path` ] definition.
///
/// e.g. `entry_defs![Anchor::entry_def()]`
///
/// The methods implemented on anchor follow the patterns that predate the Path module but `Path::from(&anchor)` is always possible to use the newer APIs.
pub struct Anchor {
pub anchor_type: String,
pub anchor_text: Option<String>,
}
/// Anchors are just a special case of path, so we can move from anchor to path losslessly.
/// We simply format the anchor structure into a string that works with the path string handling.
impl From<&Anchor> for Path {
fn from(anchor: &Anchor) -> Self {
let mut components = vec![
Component::new(ROOT.to_vec()),
Component::from(anchor.anchor_type.as_bytes().to_vec()),
];
if let Some(text) = anchor.anchor_text.as_ref() {
components.push(Component::from(text.as_bytes().to_vec()));
}
components.into()
}
}
/// Paths are more general than anchors so a path could be represented that is not a valid anchor.
/// The obvious example would be a path of binary data that is not valid utf-8 strings or a path
/// that is more than 2 levels deep.
impl TryFrom<&Path> for Anchor {
type Error = WasmError;
fn try_from(path: &Path) -> Result<Self, Self::Error> {
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 {
anchor_type: std::str::from_utf8(components[1].as_ref())
.map_err(|e| wasm_error!(SerializedBytesError::Deserialize(e.to_string())))?
.to_string(),
anchor_text: {
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()
),)
)))
}
}
}
/// Simple string interface to simple string based paths.
/// a.k.a "the anchor pattern" that predates paths by a few years.
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>,
{
let path: Path = (&Anchor {
anchor_type,
anchor_text: Some(anchor_text),
})
.into();
let path = path.typed(link_type)?;
path.ensure()?;
path.path_entry_hash()
}
/// Returns every hash in a vector from the root of an anchor.
/// Hashes are sorted in the same way that paths sort children.
pub fn list_anchor_type_addresses<T, E>(link_type: T) -> 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)?
.children()?
.into_iter()
.map(|link| link.target)
.collect();
Ok(links)
}
/// Returns every hash in a vector from the second level of an anchor.
/// Uses the string argument to build the path from the root.
/// Hashes are sorted in the same way that paths sort children.
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>,
{
let path: Path = (&Anchor {
anchor_type,
anchor_text: None,
})
.into();
let links = path
.typed(link_type)?
.children()?
.into_iter()
.map(|link| link.target)
.collect();
Ok(links)
}
/// Old version of holochain that anchors was designed for had two part link tags but now link
/// tags are a single array of bytes, so to get an external interface that is somewhat backwards
/// compatible we need to rebuild the anchors from the paths serialized into the links and then
/// return them.
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>,
{
let path: Path = (&Anchor {
anchor_type,
anchor_text: None,
})
.into();
let path = path.typed(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) {
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)]
#[test]
fn hash_path_root() {
assert_eq!(ROOT, &[0_u8, 0]);
}
#[cfg(test)]
#[test]
fn hash_path_anchor_path() {
let examples = [
(
"foo",
None,
Path::from(vec![
Component::from(vec![0, 0]),
Component::from(vec![102, 111, 111]),
]),
),
(
"foo",
Some("bar".to_string()),
Path::from(vec![
Component::from(vec![0, 0]),
Component::from(vec![102, 111, 111]),
Component::from(vec![98, 97, 114]),
]),
),
];
for (atype, text, path) in examples {
assert_eq!(
path,
(&Anchor {
anchor_type: atype.to_string(),
anchor_text: text,
})
.into(),
);
}
}
#[cfg(test)]
#[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).unwrap(),
Anchor {
anchor_type: "foo".into(),
anchor_text: Some("bar".into()),
},
);
}