1mod macros;
2
3pub use hdi;
4pub use holo_hash;
5
6use core::convert::{ TryFrom, TryInto };
7use hdi::prelude::{
8 must_get_action,
9 must_get_entry,
10 must_get_valid_record,
11 ExternResult, WasmError, WasmErrorInner,
12 Deserialize, Serialize, SerializedBytesError,
13 ActionHash, EntryHash, ExternalHash, AnyDhtHash, AnyLinkableHash,
14 Record, Action, Entry, EntryCreationAction, ActionType,
15 SignedActionHashed, EntryHashed,
16 AppEntryDef, ScopedEntryDefIndex,
17 EntryType, EntryTypesHelper,
18 Dna, AgentValidationPkg, InitZomesComplete,
20 CreateLink, DeleteLink, OpenChain, CloseChain,
21 Create, Update, Delete,
22};
23use holo_hash::AnyLinkableHashPrimitive;
24
25
26pub fn summon_valid_record(action_hash: &ActionHash) -> ExternResult<Record> {
35 must_get_valid_record(action_hash.to_owned())
36}
37
38pub fn summon_action(action_hash: &ActionHash) -> ExternResult<SignedActionHashed> {
40 must_get_action(action_hash.to_owned())
41}
42
43pub fn summon_entry(entry_hash: &EntryHash) -> ExternResult<EntryHashed> {
45 must_get_entry(entry_hash.to_owned())
46}
47
48
49fn convert_deserialize_error(error: WasmError) -> WasmError {
54 match error {
55 WasmError { error: WasmErrorInner::Serialize(SerializedBytesError::Deserialize(msg)), .. } =>
56 guest_error!(
57 format!("Could not deserialize any-linkable address to expected type: {}", msg )
58 ),
59 err => err,
60 }
61}
62
63
64pub fn trace_origin(action_address: &ActionHash) -> ExternResult<Vec<(ActionHash, Action)>> {
69 let mut history = vec![];
70 let mut next_addr = Some(action_address.to_owned());
71
72 while let Some(addr) = next_addr {
73 let record = summon_valid_record( &addr )?;
74
75 next_addr = match record.action() {
76 Action::Update(update) => Some(update.original_action_address.to_owned()),
77 Action::Create(_) => None,
78 _ => return Err(guest_error!(format!("Wrong action type '{}'", record.action().action_type() )))?,
79 };
80
81 history.push( (record.signed_action.hashed.hash, record.signed_action.hashed.content) );
82 }
83
84 Ok( history )
85}
86
87
88pub fn trace_origin_root(action_address: &ActionHash) -> ExternResult<(ActionHash, Action)> {
92 Ok( trace_origin( action_address )?.last().unwrap().to_owned() )
93}
94
95
96pub trait ScopedTypeConnector<T,U>
101where
102 ScopedEntryDefIndex: for<'a> TryFrom<&'a T, Error = WasmError>,
103{
104 fn unit() -> U;
106 fn app_entry_def() -> AppEntryDef;
108 fn check_record_entry_type(record: &Record) -> bool;
110 fn try_from_record(record: &Record) -> Result<Self, Self::Error>
112 where
113 Self: TryFrom<Record>;
114 fn to_input(&self) -> T;
116}
117
118#[macro_export]
147macro_rules! scoped_type_connector {
148 ($units:ident::$unit_name:ident, $types:ident::$name:ident( $entry:ident ) ) => {
149 scoped_type_connector!( $units::$unit_name, $types::$name, $entry );
150 };
151 ($units:ident::$unit_name:ident, $types:ident::$name:ident, $entry:ident ) => {
152 impl ScopedTypeConnector<$types,$units> for $entry {
153
154 fn unit() -> $units {
155 $units::$unit_name
156 }
157
158 fn app_entry_def () -> AppEntryDef {
159 AppEntryDef::try_from( Self::unit() ).unwrap()
162 }
163
164 fn check_record_entry_type (record: &Record) -> bool {
165 match EntryCreationAction::try_from( record.action().to_owned() ) {
166 Ok(creation_action) => match creation_action.entry_type() {
167 EntryType::App(aed) => Self::app_entry_def() == *aed,
168 _ => false,
169 },
170 _ => false,
171 }
172 }
173
174 fn try_from_record (record: &Record) -> Result<Self, WasmError> {
177 let creation_action = EntryCreationAction::try_from( record.action().to_owned() )
178 .map_err(|_| hdi_extensions::guest_error!(
179 format!("ID does not belong to a Creation Action")
180 ))?;
181
182 if let EntryType::App(aed) = creation_action.entry_type() {
183 if Self::app_entry_def() == *aed {
184 Ok( record.to_owned().try_into()? )
185 } else {
186 Err(hdi_extensions::guest_error!(
187 format!("Entry def mismatch: {:?} != {:?}", Self::app_entry_def(), aed )
188 ))
189 }
190 } else {
191 Err(hdi_extensions::guest_error!(
192 format!("Action type ({}) does not contain an entry", ActionType::from(record.action()) )
193 ))
194 }
195 }
196
197 fn to_input(&self) -> $types {
198 $types::$name(self.clone())
199 }
200 }
201 };
202}
203
204
205pub trait AnyLinkableHashTransformer : Sized {
210 fn try_from_string(input: &str) -> ExternResult<Self>;
212 fn must_be_action_hash(&self) -> ExternResult<ActionHash>;
214 fn must_be_entry_hash(&self) -> ExternResult<EntryHash>;
216}
217
218impl AnyLinkableHashTransformer for AnyLinkableHash {
219 fn try_from_string(input: &str) -> ExternResult<Self> {
220 let action_result = ActionHash::try_from( input.to_string() );
221 let entry_result = EntryHash::try_from( input.to_string() );
222 let external_result = ExternalHash::try_from( input.to_string() );
223
224 Ok(
225 match (action_result.is_ok(), entry_result.is_ok(), external_result.is_ok()) {
226 (true, false, false) => action_result.unwrap().into(),
227 (false, true, false) => entry_result.unwrap().into(),
228 (false, false, true) => external_result.unwrap().into(),
229 (false, false, false) => Err(guest_error!(
230 format!("String '{}' must be an Action or Entry hash", input )
231 ))?,
232 _ => Err(guest_error!(
233 format!("String '{}' matched multiple hash types; this should not be possible", input )
234 ))?,
235 }
236 )
237 }
238
239 fn must_be_action_hash(&self) -> ExternResult<ActionHash> {
240 match self.to_owned().into_action_hash() {
241 Some(hash) => Ok( hash ),
242 None => Err(guest_error!(
243 format!("Any-linkable hash must be an action hash; not '{}'", self )
244 ))?,
245 }
246 }
247
248 fn must_be_entry_hash(&self) -> ExternResult<EntryHash> {
249 match self.to_owned().into_entry_hash() {
250 Some(hash) => Ok( hash ),
251 None => Err(guest_error!(
252 format!("Any-linkable hash must be an entry hash; not '{}'", self )
253 ))?,
254 }
255 }
256}
257
258pub trait AnyDhtHashTransformer : Sized {
260 fn try_from_string(input: &str) -> ExternResult<Self>;
262}
263
264impl AnyDhtHashTransformer for AnyDhtHash {
265 fn try_from_string(input: &str) -> ExternResult<Self> {
266 let action_result = ActionHash::try_from( input.to_string() );
267 let entry_result = EntryHash::try_from( input.to_string() );
268
269 Ok(
270 match (action_result.is_ok(), entry_result.is_ok()) {
271 (true, false) => action_result.unwrap().into(),
272 (false, true) => entry_result.unwrap().into(),
273 (false, false) => Err(guest_error!(
274 format!("String '{}' must be an Action or Entry hash", input )
275 ))?,
276 (true, true) => Err(guest_error!(
277 format!("String '{}' matched Action and Entry hash; this should not be possible", input )
278 ))?,
279 }
280 )
281 }
282}
283
284
285pub fn summon_app_entry<T,E>(addr: &AnyLinkableHash) -> ExternResult<T>
309where
310 T: TryFrom<Record, Error = E> + TryFrom<Entry, Error = E>,
311 E: std::fmt::Debug,
312 WasmError: From<E>,
313{
314 match addr.to_owned().into_primitive() {
315 AnyLinkableHashPrimitive::Action(action_hash) => Ok(
316 summon_valid_record( &action_hash )?.try_into()
317 .map_err(|error| convert_deserialize_error( WasmError::from(error) ) )?
318 ),
319 AnyLinkableHashPrimitive::Entry(entry_hash) => Ok(
320 summon_entry( &entry_hash )?.content.try_into()
321 .map_err(|error| convert_deserialize_error( WasmError::from(error) ) )?
322 ),
323 AnyLinkableHashPrimitive::External(external_hash) => Err(guest_error!(
324 format!("Cannot get an entry from any-linkable external hash ({})", external_hash )
325 ))?,
326 }
327}
328
329pub fn verify_app_entry_struct<T>(addr: &AnyLinkableHash) -> ExternResult<()>
350where
351 T: TryFrom<Record, Error = WasmError> + TryFrom<Entry, Error = WasmError>,
352{
353 let _ : T = summon_app_entry( addr )?;
354
355 Ok(())
356}
357
358pub fn summon_record_type(
360 action_addr: &ActionHash,
361 action_type: &ActionType
362) -> ExternResult<Record> {
363 let record = summon_valid_record( action_addr )?;
364
365 if record.action().action_type() != *action_type {
366 Err(guest_error!(format!("Action address ({}) is not a {} record", action_addr, action_type )))?
367 }
368
369 Ok( record )
370}
371
372macro_rules! get_action_type {
373 ( $action_type:ident, $fn_name:ident ) => {
374 #[doc = concat!("Get an action address expecting it to be a [`Action::", stringify!($action_type), "`]")]
375 pub fn $fn_name(
376 action_addr: &ActionHash,
377 ) -> ExternResult<$action_type> {
378 match summon_record_type( action_addr, &ActionType::$action_type )?.signed_action.hashed.content {
379 Action::$action_type( action_inner ) => Ok( action_inner ),
380 _ => Err(guest_error!("This should be unreachable".to_string())),
381 }
382 }
383 };
384}
385
386get_action_type!( Dna, summon_dna_action );
387get_action_type!( AgentValidationPkg, summon_agent_validation_pkg_action );
388get_action_type!( InitZomesComplete, summon_init_zomes_complete_action );
389get_action_type!( CreateLink, summon_create_link_action );
390get_action_type!( DeleteLink, summon_delete_link_action );
391get_action_type!( OpenChain, summon_open_chain_action );
392get_action_type!( CloseChain, summon_close_chain_action );
393get_action_type!( Create, summon_create_action );
394get_action_type!( Update, summon_update_action );
395get_action_type!( Delete, summon_delete_action );
396
397
398pub fn summon_creation_action(action_addr: &ActionHash) -> ExternResult<EntryCreationAction> {
400 let create_record = summon_valid_record( action_addr )?;
401 match create_record.signed_action.hashed.content {
402 Action::Create(create) => Ok( create.into() ),
403 Action::Update(update) => Ok( update.into() ),
404 _ => Err(guest_error!(format!("Action address ({}) is not a create action", action_addr ))),
405 }
406}
407
408pub trait ActionTransformer : Sized {
410 fn summon_app_entry<ET>(&self) -> ExternResult<ET>
436 where
437 ET: EntryTypesHelper,
438 WasmError: From<<ET as EntryTypesHelper>::Error>;
439}
440
441impl ActionTransformer for Action {
442 fn summon_app_entry<ET>(&self) -> ExternResult<ET>
443 where
444 ET: EntryTypesHelper,
445 WasmError: From<<ET as EntryTypesHelper>::Error>,
446 {
447 let action = EntryCreationAction::try_from( self.to_owned() )
448 .map_err(|err| guest_error!(err.0))?;
449 let entry_def = detect_app_entry_def( &action )?;
450 let entry = summon_entry( action.entry_hash() )?.content;
451
452 ET::deserialize_from_type(
453 entry_def.zome_index.clone(),
454 entry_def.entry_index.clone(),
455 &entry,
456 )?.ok_or(guest_error!(
457 format!("No match for entry def ({:?}) in expected entry types", entry_def )
458 ))
459 }
460}
461
462
463pub fn detect_app_entry_def<A>(action: &A) -> ExternResult<AppEntryDef>
468where
469 A: Into<EntryCreationAction> + Clone,
470{
471 let action : EntryCreationAction = action.to_owned().into();
472 match action.entry_type().to_owned() {
473 EntryType::App(app_entry_def) => Ok( app_entry_def ),
474 entry_type => Err(guest_error!(
475 format!("Expected an app entry type; not {:?}", entry_type )
476 )),
477 }
478}
479
480pub fn detect_app_entry_unit<ETU,A>(action: &A) -> ExternResult<ETU>
482where
483 ETU: TryFrom<ScopedEntryDefIndex, Error = WasmError>,
484 A: Into<EntryCreationAction> + Clone,
485{
486 let action : EntryCreationAction = action.to_owned().into();
487 let entry_def = detect_app_entry_def( &action )?;
488 ETU::try_from(ScopedEntryDefIndex {
489 zome_index: entry_def.zome_index,
490 zome_type: entry_def.entry_index,
491 })
492}
493
494
495#[derive(Clone, Serialize, Deserialize, Debug)]
500pub struct LinkDirectionInput {
501 pub base: AnyLinkableHash,
502 pub target: AnyLinkableHash,
503}