1mod errors;
8mod entities;
9mod utils;
10
11use std::convert::TryFrom;
12use hdk::prelude::*;
13
14pub use entities::{ Entity, EmptyEntity, EntityType, EntryModel };
15pub use errors::{ UtilsResult, UtilsError };
16pub use utils::{
17 now, find_latest_link, path_from_collection,
18 trace_action_history, to_entry_type,
19};
20
21
22
23pub fn get_origin_address(addr: &ActionHash) -> UtilsResult<EntryHash> {
25 let chain = trace_action_history( addr )?;
26
27 Ok( chain.last().unwrap().1.to_owned() )
29}
30
31pub fn fetch_record(addr: &EntryHash) -> UtilsResult<(ActionHash, Record)> {
33 let record = get( addr.to_owned(), GetOptions::latest() )?
34 .ok_or( UtilsError::EntryNotFoundError(addr.to_owned(), Some("".to_string())) )?;
35
36 Ok( (record.action_address().to_owned(), record) )
37}
38
39pub fn find_earliest_action(updates: Vec<SignedHashed<Action>>) -> Option<SignedHashed<Action>> {
41 if updates.len() == 0 {
42 None
43 }
44 else {
45 Some( updates.iter()
46 .fold( None, |acc, sh| {
47 let ts = sh.action().timestamp();
48 match acc {
49 None => Some( (ts, sh.to_owned()) ),
50 Some(current) => {
51 Some(match current.0 < ts {
52 true => current,
53 false => (ts, sh.to_owned()),
54 })
55 }
56 }
57 }).unwrap().1 )
58 }
59}
60
61
62pub fn follow_updates(hash: &ActionHash, trace: Option<Vec<ActionHash>>) -> UtilsResult<Vec<ActionHash>> {
64 let mut history = trace.unwrap_or( Vec::new() );
65 history.push( hash.to_owned() );
66
67 let details = get_details( hash.to_owned(), GetOptions::latest() )?
68 .ok_or( UtilsError::ActionNotFoundError(hash.to_owned(), Some("".to_string())) )?;
69 let updates = match details {
70 Details::Record(details) => details.updates,
71 Details::Entry(details) => details.updates,
72 };
73
74 Ok( match find_earliest_action( updates ) {
75 None => history,
76 Some(next_update) => follow_updates( next_update.action_address(), Some(history) )?,
77 })
78}
79
80pub fn fetch_record_latest(id: &EntryHash) -> UtilsResult<(ActionHash, Record)> {
82 let (action_hash, first_record) = fetch_record( id )?;
83
84 match first_record.action() {
85 Action::Create(_) => (),
86 _ => Err(UtilsError::NotOriginEntryError(action_hash.to_owned()))?,
87 }
88
89 let updates = follow_updates( &action_hash, None )?;
90 let latest_action_hash = updates.last().unwrap();
91 let record = get( latest_action_hash.to_owned(), GetOptions::latest() )?
92 .ok_or( UtilsError::ActionNotFoundError(action_hash.to_owned(), Some("".to_string())) )?;
93
94 Ok( (action_hash, record) )
95}
96
97
98
99pub fn create_entity<T,I,E>(entry: &T) -> UtilsResult<Entity<T>>
101where
102 ScopedEntryDefIndex: for<'a> TryFrom<&'a I, Error = WasmError>,
103 EntryVisibility: for<'a> From<&'a I>,
104 Entry: TryFrom<I, Error = E>,
105 Entry: TryFrom<T, Error = E>,
106 WasmError: From<E>,
107 T: Clone + EntryModel<I>,
108{
109 let entry_hash = hash_entry( entry.to_owned() )?;
110 let action_hash = create_entry( entry.to_input() )?;
111
112 Ok(Entity {
113 id: entry_hash.to_owned(),
114 address: entry_hash,
115 action: action_hash,
116 ctype: entry.get_type(),
117 content: entry.to_owned(),
118 })
119}
120
121pub fn get_entity<I,ET>(id: &EntryHash) -> UtilsResult<Entity<I>>
123where
124 I: TryFrom<Record, Error = WasmError> + Clone + EntryModel<ET>,
125 Entry: TryFrom<I, Error = WasmError>,
126 ScopedEntryDefIndex: for<'a> TryFrom<&'a ET, Error = WasmError>,
127{
128 let (_, record) = fetch_record_latest( id )?;
129 let to_type_input = record.to_owned();
130 let address = record
131 .action()
132 .entry_hash()
133 .ok_or(UtilsError::EntryNotFoundError(id.to_owned(), None))?;
134
135 let content : I = to_entry_type( to_type_input )?;
136
137 Ok(Entity {
138 id: id.to_owned(),
139 action: record.action_address().to_owned(),
140 address: address.to_owned(),
141 ctype: content.get_type(),
142 content: content,
143 })
144}
145
146pub fn update_entity<T,I,F,E>(addr: &ActionHash, callback: F) -> UtilsResult<Entity<T>>
148where
149 ScopedEntryDefIndex: for<'a> TryFrom<&'a I, Error = WasmError>,
150 Entry: TryFrom<I, Error = E>,
151 Entry: TryFrom<T, Error = E>,
152 WasmError: From<E>,
153 T: TryFrom<Record, Error = WasmError>,
154 T: Clone + EntryModel<I>,
155 F: FnOnce(T, Record) -> UtilsResult<T>,
156{
157 let id = get_origin_address( &addr )?;
160 let record = get( addr.to_owned(), GetOptions::latest() )?
161 .ok_or( UtilsError::ActionNotFoundError(addr.to_owned(), Some("Given origin for update is not found".to_string())) )?;
162
163 let current : T = to_entry_type( record.clone() )?;
164 let updated_entry = callback( current, record.clone() )?;
165
166 let entry_hash = hash_entry( updated_entry.to_owned() )?;
167 let action_hash = update_entry( addr.to_owned(), updated_entry.to_input() )?;
168
169 Ok(Entity {
170 id: id,
171 action: action_hash,
172 address: entry_hash,
173 ctype: updated_entry.get_type(),
174 content: updated_entry,
175 })
176}
177
178pub fn delete_entity<T,ET>(id: &EntryHash) -> UtilsResult<ActionHash>
180where
181 T: TryFrom<Record, Error = WasmError> + Clone + EntryModel<ET>,
182 Entry: TryFrom<T, Error = WasmError>,
183 ScopedEntryDefIndex: for<'a> TryFrom<&'a ET, Error = WasmError>,
184{
185 let (action_hash, record) = fetch_record( id )?;
186 let _ : T = to_entry_type( record )?;
187 let delete_hash = delete_entry( action_hash )?;
188
189 Ok( delete_hash )
190}
191
192
193pub fn get_entities<T,LT,ET>(id: &EntryHash, link_type: LT, tag: Option<Vec<u8>>) -> UtilsResult<Vec<Entity<T>>>
195where
196 T: TryFrom<Record, Error = WasmError> + Clone + EntryModel<ET>,
197 LT: LinkTypeFilterExt,
198 Entry: TryFrom<T, Error = WasmError>,
199 ScopedEntryDefIndex: for<'a> TryFrom<&'a ET, Error = WasmError>,
200{
201 let links_result = get_links(
202 id.to_owned(),
203 link_type,
204 tag.map( |tag| LinkTag::new( tag ) )
205 );
206 debug!("get_entities: {:?}", links_result );
207 let links = links_result?;
208
209 let list = links.into_iter()
210 .filter_map(|link| {
211 link.target.into_entry_hash()
212 .and_then( |target| get_entity( &target ).ok() )
213 })
214 .collect();
215
216 Ok(list)
217}