pub use hdi_extensions::hdi;
pub use hdi_extensions::holo_hash;
pub use hdk;
pub use hdi_extensions;
use core::convert::{ TryFrom, TryInto };
use hdi_extensions::{
summon_action,
summon_entry,
};
use hdk::prelude::{
get, get_details, agent_info,
debug, wasm_error,
Serialize, Deserialize,
ExternResult, WasmError, WasmErrorInner, GetOptions,
Record, Action, Details, RecordDetails, SignedHashed,
LinkTypeFilter, LinkTypeFilterExt, LinkTag,
};
use holo_hash::{
AgentPubKey, ActionHash, AnyDhtHash, AnyLinkableHash,
AnyDhtHashPrimitive, AnyLinkableHashPrimitive,
};
use thiserror::Error;
use hdi_extensions::*;
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct Entity<T>(
pub MorphAddr,
pub T,
)
where
T: Clone;
impl<T> Entity<T>
where
T: Clone,
{
pub fn identity(&self) -> &ActionHash {
self.0.identity()
}
pub fn revision(&self) -> &ActionHash {
self.0.revision()
}
pub fn is_origin(&self) -> bool {
self.0.is_origin()
}
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct MorphAddr(
pub ActionHash,
pub ActionHash,
);
impl MorphAddr {
pub fn identity(&self) -> &ActionHash {
&self.0
}
pub fn revision(&self) -> &ActionHash {
&self.1
}
pub fn is_origin(&self) -> bool {
self.0 == self.1
}
}
#[derive(Debug, Error)]
pub enum HdkExtError<'a> {
#[error("Record not found @ address {0}")]
RecordNotFound(&'a AnyDhtHash),
#[error("No entry in record ({0})")]
RecordHasNoEntry(&'a ActionHash),
#[error("Expected an action hash, not an entry hash: {0}")]
ExpectedRecordNotEntry(&'a ActionHash),
}
impl<'a> From<HdkExtError<'a>> for WasmError {
fn from(error: HdkExtError) -> Self {
wasm_error!(WasmErrorInner::Guest( format!("{}", error ) ))
}
}
pub fn agent_id() -> ExternResult<AgentPubKey> {
Ok( agent_info()?.agent_initial_pubkey )
}
pub fn must_get<T>(addr: &T) -> ExternResult<Record>
where
T: Clone + std::fmt::Debug,
AnyDhtHash: From<T>,
{
Ok(
get( addr.to_owned(), GetOptions::network() )?
.ok_or(HdkExtError::RecordNotFound(&addr.to_owned().into()))?
)
}
pub fn must_get_record_details(action: &ActionHash) -> ExternResult<RecordDetails> {
let details = get_details( action.to_owned(), GetOptions::network() )?
.ok_or(HdkExtError::RecordNotFound(&action.to_owned().into()))?;
match details {
Details::Record(record_details) => Ok( record_details ),
Details::Entry(_) => Err(HdkExtError::ExpectedRecordNotEntry(action))?,
}
}
pub fn exists<T>(addr: &T) -> ExternResult<bool>
where
T: Clone + std::fmt::Debug,
AnyDhtHash: From<T>,
{
debug!("Checking if address {:?} exists", addr );
Ok(
match AnyDhtHash::from(addr.to_owned()).into_primitive() {
AnyDhtHashPrimitive::Action(addr) => summon_action( &addr ).is_ok(),
AnyDhtHashPrimitive::Entry(addr) => summon_entry( &addr ).is_ok(),
}
)
}
pub fn available<T>(addr: &T) -> ExternResult<bool>
where
T: Clone + std::fmt::Debug,
AnyDhtHash: From<T>,
{
debug!("Checking if address {:?} is available", addr );
Ok( get( addr.to_owned(), GetOptions::network() )?.is_some() )
}
pub fn resolve_action_addr<T>(addr: &T) -> ExternResult<ActionHash>
where
T: Into<AnyLinkableHash> + Clone,
{
let addr : AnyLinkableHash = addr.to_owned().into();
match addr.into_primitive() {
AnyLinkableHashPrimitive::Entry(entry_hash) => {
Ok(
must_get( &entry_hash )?.action_address().to_owned()
)
},
AnyLinkableHashPrimitive::Action(action_hash) => Ok( action_hash ),
AnyLinkableHashPrimitive::External(external_hash) => Err(guest_error!(
format!("External hash ({}) will not have a corresponding action", external_hash )
)),
}
}
pub fn follow_evolutions(action_address: &ActionHash) -> ExternResult<Vec<ActionHash>> {
let mut evolutions = vec![];
let mut next_addr = Some(action_address.to_owned());
while let Some(addr) = next_addr {
let details = must_get_record_details( &addr )?;
let maybe_next_update = details.updates.iter()
.min_by_key(|sa| sa.action().timestamp() );
next_addr = match maybe_next_update {
Some(signed_action) => Some(signed_action.hashed.hash.to_owned()),
None => None,
};
evolutions.push( addr );
}
Ok( evolutions )
}
pub fn follow_evolutions_selector<F>(
action_address: &ActionHash,
selector: F
) -> ExternResult<Vec<ActionHash>>
where
F: Fn(Vec<SignedHashed<Action>>) -> ExternResult<Option<ActionHash>>,
{
let mut evolutions = vec![];
let mut next_addr = Some(action_address.to_owned());
while let Some(addr) = next_addr {
let details = must_get_record_details( &addr )?;
next_addr = selector( details.updates )?;
evolutions.push( addr );
}
Ok( evolutions )
}
pub fn follow_evolutions_using_authorities(
action_address: &ActionHash,
authors: &Vec<AgentPubKey>
) -> ExternResult<Vec<ActionHash>> {
let evolutions = follow_evolutions_selector( action_address, |updates| {
let updates_count = updates.len();
let valid_updates : Vec<SignedHashed<Action>> = updates
.into_iter()
.filter(|sa| {
debug!(
"Checking authorities for author '{}': {:?}",
sa.action().author(),
authors
);
authors.contains( sa.action().author() )
})
.collect();
debug!(
"Filtered {}/{} updates",
updates_count - valid_updates.len(),
updates_count
);
let maybe_next_update = valid_updates.iter()
.min_by_key(|sa| sa.action().timestamp() );
Ok(
match maybe_next_update {
Some(signed_action) => Some(signed_action.hashed.hash.to_owned()),
None => None,
}
)
})?;
Ok( evolutions )
}
pub fn follow_evolutions_using_authorities_with_exceptions(
action_address: &ActionHash,
authors: &Vec<AgentPubKey>,
exceptions: &Vec<ActionHash>
) -> ExternResult<Vec<ActionHash>> {
let evolutions = follow_evolutions_selector( action_address, |updates| {
let updates_count = updates.len();
let valid_updates : Vec<SignedHashed<Action>> = updates
.into_iter()
.filter(|sa| {
debug!(
"Checking authorities for author '{}' or an action exception '{}'",
sa.action().author(),
sa.action_address()
);
authors.contains( sa.action().author() ) || exceptions.contains( sa.action_address() )
})
.collect();
debug!(
"Filtered {}/{} updates",
updates_count - valid_updates.len(),
updates_count
);
let maybe_next_update = valid_updates.iter()
.min_by_key(|sa| sa.action().timestamp() );
Ok(
match maybe_next_update {
Some(signed_action) => Some(signed_action.hashed.hash.to_owned()),
None => None,
}
)
})?;
Ok( evolutions )
}
#[derive(Clone, Serialize, Debug)]
#[serde(untagged)]
pub enum EvolutionFilteringStrategy {
Unfiltered,
AuthoritiesFilter(Vec<AgentPubKey>),
AuthoritiesExceptionsFilter(Vec<AgentPubKey>, Vec<ActionHash>),
ExceptionsFilter(Vec<ActionHash>),
}
impl Default for EvolutionFilteringStrategy {
fn default() -> Self {
EvolutionFilteringStrategy::Unfiltered
}
}
impl<'de> serde::Deserialize<'de> for EvolutionFilteringStrategy {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let buffer : FollowEvolutionsInputBuffer = Deserialize::deserialize(deserializer)?;
Ok( buffer.into() )
}
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct FollowEvolutionsInputBuffer {
pub authors: Option<Vec<AgentPubKey>>,
pub exceptions: Option<Vec<ActionHash>>,
}
impl From<FollowEvolutionsInputBuffer> for EvolutionFilteringStrategy {
fn from(buffer: FollowEvolutionsInputBuffer) -> Self {
match (buffer.authors, buffer.exceptions) {
(None, None) => Self::Unfiltered,
(Some(authors), None) => Self::AuthoritiesFilter(authors),
(None, Some(exceptions)) => Self::ExceptionsFilter(exceptions),
(Some(authors), Some(exceptions)) => Self::AuthoritiesExceptionsFilter(authors, exceptions),
}
}
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct GetEntityInput {
pub id: ActionHash,
#[serde(default)]
pub follow_strategy: EvolutionFilteringStrategy,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct UpdateEntryInput<T> {
pub base: ActionHash,
pub entry: T,
}
#[derive(Clone, Serialize, Deserialize, Debug)]
pub struct GetLinksInputBuffer {
pub base: AnyLinkableHash,
pub target: AnyLinkableHash,
pub link_type: String,
pub tag: Option<String>,
}
#[derive(Clone, Serialize, Debug)]
pub struct GetLinksInput<T>
where
T: LinkTypeFilterExt + TryFrom<String, Error = WasmError> + Clone,
{
pub base: AnyLinkableHash,
pub target: AnyLinkableHash,
pub link_type_filter: LinkTypeFilter,
pub tag: Option<LinkTag>,
pub link_type: Option<T>,
}
impl<T> TryFrom<GetLinksInputBuffer> for GetLinksInput<T>
where
T: LinkTypeFilterExt + TryFrom<String, Error = WasmError> + Clone,
{
type Error = WasmError;
fn try_from(buffer: GetLinksInputBuffer) -> Result<Self, Self::Error> {
let (link_type, link_type_filter) = match buffer.link_type.as_str() {
".." => ( None, (..).try_into_filter()? ),
name => {
let link_type = T::try_from( name.to_string() )?;
( Some(link_type.clone()), link_type.try_into_filter()? )
},
};
Ok(Self {
base: buffer.base,
target: buffer.target,
tag: buffer.tag.map(|text| text.into_bytes().into() ),
link_type,
link_type_filter,
})
}
}
impl<'de,T> serde::Deserialize<'de> for GetLinksInput<T>
where
T: LinkTypeFilterExt + TryFrom<String, Error = WasmError> + Clone,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let buffer : GetLinksInputBuffer = Deserialize::deserialize(deserializer)?;
let error_msg = format!("Buffer could not be converted: {:#?}", buffer );
Ok(
buffer.try_into()
.or(Err(serde::de::Error::custom(error_msg)))?
)
}
}