1pub use hdi_extensions::hdi;
2pub use hdi_extensions::holo_hash;
3pub use hdk;
4pub use hdi_extensions;
5
6use core::convert::{ TryFrom, TryInto };
7use hdi_extensions::{
8 summon_action,
9 summon_entry,
10};
11use hdk::prelude::{
12 get, get_details, agent_info,
13 debug, wasm_error,
14 Serialize, Deserialize,
15 ExternResult, WasmError, WasmErrorInner, GetOptions,
16 Record, Action, Details, RecordDetails, SignedHashed,
17 LinkTypeFilter, LinkTypeFilterExt, LinkTag,
18};
19use holo_hash::{
20 AgentPubKey, ActionHash, AnyDhtHash, AnyLinkableHash,
21 AnyDhtHashPrimitive, AnyLinkableHashPrimitive,
22};
23use thiserror::Error;
24use hdi_extensions::*;
25
26
27
28#[derive(Clone, Serialize, Deserialize, Debug)]
63pub struct Entity<T>(
64 pub MorphAddr,
66 pub T,
68)
69where
70 T: Clone;
71
72impl<T> Entity<T>
73where
74 T: Clone,
75{
76 pub fn identity(&self) -> &ActionHash {
78 self.0.identity()
79 }
80
81 pub fn revision(&self) -> &ActionHash {
83 self.0.revision()
84 }
85
86 pub fn is_origin(&self) -> bool {
88 self.0.is_origin()
89 }
90}
91
92#[derive(Clone, Serialize, Deserialize, Debug)]
116pub struct MorphAddr(
117 pub ActionHash,
119 pub ActionHash,
121);
122
123impl MorphAddr {
124 pub fn identity(&self) -> &ActionHash {
126 &self.0
127 }
128
129 pub fn revision(&self) -> &ActionHash {
131 &self.1
132 }
133
134 pub fn is_origin(&self) -> bool {
136 self.0 == self.1
137 }
138}
139
140
141
142#[derive(Debug, Error)]
146pub enum HdkExtError<'a> {
147 #[error("Record not found @ address {0}")]
148 RecordNotFound(&'a AnyDhtHash),
149 #[error("No entry in record ({0})")]
150 RecordHasNoEntry(&'a ActionHash),
151 #[error("Expected an action hash, not an entry hash: {0}")]
152 ExpectedRecordNotEntry(&'a ActionHash),
153}
154
155impl<'a> From<HdkExtError<'a>> for WasmError {
156 fn from(error: HdkExtError) -> Self {
157 wasm_error!(WasmErrorInner::Guest( format!("{}", error ) ))
158 }
159}
160
161
162
163pub fn agent_id() -> ExternResult<AgentPubKey> {
168 Ok( agent_info()?.agent_initial_pubkey )
169}
170
171
172
173pub fn must_get<T>(addr: &T) -> ExternResult<Record>
184where
185 T: Clone + std::fmt::Debug,
186 AnyDhtHash: From<T>,
187{
188 Ok(
189 get( addr.to_owned(), GetOptions::network() )?
190 .ok_or(HdkExtError::RecordNotFound(&addr.to_owned().into()))?
191 )
192}
193
194
195pub fn must_get_record_details(action: &ActionHash) -> ExternResult<RecordDetails> {
199 let details = get_details( action.to_owned(), GetOptions::network() )?
200 .ok_or(HdkExtError::RecordNotFound(&action.to_owned().into()))?;
201
202 match details {
203 Details::Record(record_details) => Ok( record_details ),
204 Details::Entry(_) => Err(HdkExtError::ExpectedRecordNotEntry(action))?,
205 }
206}
207
208
209pub fn exists<T>(addr: &T) -> ExternResult<bool>
211where
212 T: Clone + std::fmt::Debug,
213 AnyDhtHash: From<T>,
214{
215 debug!("Checking if address {:?} exists", addr );
216 Ok(
217 match AnyDhtHash::from(addr.to_owned()).into_primitive() {
218 AnyDhtHashPrimitive::Action(addr) => summon_action( &addr ).is_ok(),
219 AnyDhtHashPrimitive::Entry(addr) => summon_entry( &addr ).is_ok(),
220 }
221 )
222}
223
224
225pub fn available<T>(addr: &T) -> ExternResult<bool>
227where
228 T: Clone + std::fmt::Debug,
229 AnyDhtHash: From<T>,
230{
231 debug!("Checking if address {:?} is available", addr );
232 Ok( get( addr.to_owned(), GetOptions::network() )?.is_some() )
233}
234
235
236
237pub fn resolve_action_addr<T>(addr: &T) -> ExternResult<ActionHash>
247where
248 T: Into<AnyLinkableHash> + Clone,
249{
250 let addr : AnyLinkableHash = addr.to_owned().into();
251 match addr.into_primitive() {
252 AnyLinkableHashPrimitive::Entry(entry_hash) => {
253 Ok(
254 must_get( &entry_hash )?.action_address().to_owned()
255 )
256 },
257 AnyLinkableHashPrimitive::Action(action_hash) => Ok( action_hash ),
258 AnyLinkableHashPrimitive::External(external_hash) => Err(guest_error!(
259 format!("External hash ({}) will not have a corresponding action", external_hash )
260 )),
261 }
262}
263
264
265pub fn follow_evolutions(action_address: &ActionHash) -> ExternResult<Vec<ActionHash>> {
271 let mut evolutions = vec![];
272 let mut next_addr = Some(action_address.to_owned());
273
274 while let Some(addr) = next_addr {
275 let details = must_get_record_details( &addr )?;
276 let maybe_next_update = details.updates.iter()
277 .min_by_key(|sa| sa.action().timestamp() );
278
279 next_addr = match maybe_next_update {
280 Some(signed_action) => Some(signed_action.hashed.hash.to_owned()),
281 None => None,
282 };
283
284 evolutions.push( addr );
285 }
286
287 Ok( evolutions )
288}
289
290
291pub fn follow_evolutions_selector<F>(
293 action_address: &ActionHash,
294 selector: F
295) -> ExternResult<Vec<ActionHash>>
296where
297 F: Fn(Vec<SignedHashed<Action>>) -> ExternResult<Option<ActionHash>>,
298{
299 let mut evolutions = vec![];
300 let mut next_addr = Some(action_address.to_owned());
301
302 while let Some(addr) = next_addr {
303 let details = must_get_record_details( &addr )?;
304 next_addr = selector( details.updates )?;
305
306 evolutions.push( addr );
307 }
308
309 Ok( evolutions )
310}
311
312
313pub fn follow_evolutions_using_authorities(
315 action_address: &ActionHash,
316 authors: &Vec<AgentPubKey>
317) -> ExternResult<Vec<ActionHash>> {
318 let evolutions = follow_evolutions_selector( action_address, |updates| {
319 let updates_count = updates.len();
320 let valid_updates : Vec<SignedHashed<Action>> = updates
321 .into_iter()
322 .filter(|sa| {
323 debug!(
324 "Checking authorities for author '{}': {:?}",
325 sa.action().author(),
326 authors
327 );
328 authors.contains( sa.action().author() )
329 })
330 .collect();
331
332 debug!(
333 "Filtered {}/{} updates",
334 updates_count - valid_updates.len(),
335 updates_count
336 );
337 let maybe_next_update = valid_updates.iter()
338 .min_by_key(|sa| sa.action().timestamp() );
339
340 Ok(
341 match maybe_next_update {
342 Some(signed_action) => Some(signed_action.hashed.hash.to_owned()),
343 None => None,
344 }
345 )
346 })?;
347
348 Ok( evolutions )
349}
350
351
352pub fn follow_evolutions_using_authorities_with_exceptions(
354 action_address: &ActionHash,
355 authors: &Vec<AgentPubKey>,
356 exceptions: &Vec<ActionHash>
357) -> ExternResult<Vec<ActionHash>> {
358 let evolutions = follow_evolutions_selector( action_address, |updates| {
359 let updates_count = updates.len();
360 let valid_updates : Vec<SignedHashed<Action>> = updates
361 .into_iter()
362 .filter(|sa| {
363 debug!(
364 "Checking authorities for author '{}' or an action exception '{}'",
365 sa.action().author(),
366 sa.action_address()
367 );
368 authors.contains( sa.action().author() ) || exceptions.contains( sa.action_address() )
369 })
370 .collect();
371
372 debug!(
373 "Filtered {}/{} updates",
374 updates_count - valid_updates.len(),
375 updates_count
376 );
377 let maybe_next_update = valid_updates.iter()
378 .min_by_key(|sa| sa.action().timestamp() );
379
380 Ok(
381 match maybe_next_update {
382 Some(signed_action) => Some(signed_action.hashed.hash.to_owned()),
383 None => None,
384 }
385 )
386 })?;
387
388 Ok( evolutions )
389}
390
391
392#[derive(Clone, Serialize, Debug)]
394#[serde(untagged)]
395pub enum EvolutionFilteringStrategy {
396 Unfiltered,
398 AuthoritiesFilter(Vec<AgentPubKey>),
400 AuthoritiesExceptionsFilter(Vec<AgentPubKey>, Vec<ActionHash>),
402 ExceptionsFilter(Vec<ActionHash>),
404}
405
406impl Default for EvolutionFilteringStrategy {
407 fn default() -> Self {
408 EvolutionFilteringStrategy::Unfiltered
409 }
410}
411
412impl<'de> serde::Deserialize<'de> for EvolutionFilteringStrategy {
413 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
414 where
415 D: serde::Deserializer<'de>,
416 {
417 let buffer : FollowEvolutionsInputBuffer = Deserialize::deserialize(deserializer)?;
418
419 Ok( buffer.into() )
420 }
421}
422
423#[derive(Clone, Serialize, Deserialize, Debug)]
425pub struct FollowEvolutionsInputBuffer {
426 pub authors: Option<Vec<AgentPubKey>>,
427 pub exceptions: Option<Vec<ActionHash>>,
428}
429
430impl From<FollowEvolutionsInputBuffer> for EvolutionFilteringStrategy {
431 fn from(buffer: FollowEvolutionsInputBuffer) -> Self {
432 match (buffer.authors, buffer.exceptions) {
433 (None, None) => Self::Unfiltered,
434 (Some(authors), None) => Self::AuthoritiesFilter(authors),
435 (None, Some(exceptions)) => Self::ExceptionsFilter(exceptions),
436 (Some(authors), Some(exceptions)) => Self::AuthoritiesExceptionsFilter(authors, exceptions),
437 }
438 }
439}
440
441
442#[derive(Clone, Serialize, Deserialize, Debug)]
447pub struct GetEntityInput {
448 pub id: ActionHash,
449 #[serde(default)]
450 pub follow_strategy: EvolutionFilteringStrategy,
451}
452
453#[derive(Clone, Serialize, Deserialize, Debug)]
455pub struct UpdateEntryInput<T> {
456 pub base: ActionHash,
457 pub entry: T,
458}
459
460#[derive(Clone, Serialize, Deserialize, Debug)]
462pub struct GetLinksInputBuffer {
463 pub base: AnyLinkableHash,
464 pub target: AnyLinkableHash,
465 pub link_type: String,
466 pub tag: Option<String>,
467}
468
469#[derive(Clone, Serialize, Debug)]
471pub struct GetLinksInput<T>
472where
473 T: LinkTypeFilterExt + TryFrom<String, Error = WasmError> + Clone,
474{
475 pub base: AnyLinkableHash,
476 pub target: AnyLinkableHash,
477 pub link_type_filter: LinkTypeFilter,
478 pub tag: Option<LinkTag>,
479 pub link_type: Option<T>,
480}
481
482impl<T> TryFrom<GetLinksInputBuffer> for GetLinksInput<T>
483where
484 T: LinkTypeFilterExt + TryFrom<String, Error = WasmError> + Clone,
485{
486 type Error = WasmError;
487
488 fn try_from(buffer: GetLinksInputBuffer) -> Result<Self, Self::Error> {
489 let (link_type, link_type_filter) = match buffer.link_type.as_str() {
490 ".." => ( None, (..).try_into_filter()? ),
491 name => {
492 let link_type = T::try_from( name.to_string() )?;
493 ( Some(link_type.clone()), link_type.try_into_filter()? )
494 },
495 };
496
497 Ok(Self {
498 base: buffer.base,
499 target: buffer.target,
500 tag: buffer.tag.map(|text| text.into_bytes().into() ),
501 link_type,
502 link_type_filter,
503 })
504 }
505}
506
507impl<'de,T> serde::Deserialize<'de> for GetLinksInput<T>
508where
509 T: LinkTypeFilterExt + TryFrom<String, Error = WasmError> + Clone,
510{
511 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
512 where
513 D: serde::Deserializer<'de>,
514 {
515 let buffer : GetLinksInputBuffer = Deserialize::deserialize(deserializer)?;
516 let error_msg = format!("Buffer could not be converted: {:#?}", buffer );
517
518 Ok(
519 buffer.try_into()
520 .or(Err(serde::de::Error::custom(error_msg)))?
521 )
522 }
523}