1use crate::prelude::*;
3
4#[cfg(test)]
5mod test;
6
7pub trait OpHelper {
21 fn flattened<ET, LT>(&self) -> Result<FlatOp<ET, LT>, WasmError>
24 where
25 ET: EntryTypesHelper + UnitEnum,
26 <ET as UnitEnum>::Unit: Into<ZomeEntryTypesKey>,
27 LT: LinkTypesHelper,
28 WasmError: From<<ET as EntryTypesHelper>::Error>,
29 WasmError: From<<LT as LinkTypesHelper>::Error>;
30}
31
32#[derive(Debug)]
35enum ActivityEntry<Unit> {
36 App { entry_type: Option<Unit> },
37 PrivateApp { entry_type: Option<Unit> },
38 Agent(AgentPubKey),
39 CapClaim(EntryHash),
40 CapGrant(EntryHash),
41}
42
43impl OpHelper for Op {
44 fn flattened<ET, LT>(&self) -> Result<FlatOp<ET, LT>, WasmError>
45 where
46 ET: EntryTypesHelper + UnitEnum,
47 <ET as UnitEnum>::Unit: Into<ZomeEntryTypesKey>,
48 LT: LinkTypesHelper,
49 WasmError: From<<ET as EntryTypesHelper>::Error>,
50 WasmError: From<<LT as LinkTypesHelper>::Error>,
51 {
52 match self {
53 Op::StoreRecord(StoreRecord { record }) => {
54 let r = match record.action() {
55 Action::Dna(action) => OpRecord::Dna {
56 dna_hash: action.hash.clone(),
57 action: action.clone(),
58 },
59 Action::AgentValidationPkg(action) => {
60 let AgentValidationPkg { membrane_proof, .. } = action;
61 OpRecord::AgentValidationPkg {
62 membrane_proof: membrane_proof.clone(),
63 action: action.clone(),
64 }
65 }
66 Action::InitZomesComplete(action) => OpRecord::InitZomesComplete {
67 action: action.clone(),
68 },
69 Action::CreateLink(action) => {
70 let CreateLink {
71 zome_index,
72 link_type,
73 base_address,
74 target_address,
75 tag,
76 ..
77 } = action;
78 let link_type = in_scope_link_type(*zome_index, *link_type)?;
79 OpRecord::CreateLink {
80 base_address: base_address.clone(),
81 target_address: target_address.clone(),
82 tag: tag.clone(),
83 link_type,
84 action: action.clone(),
85 }
86 }
87 Action::DeleteLink(action) => {
88 let DeleteLink {
89 base_address,
90 link_add_address,
91 ..
92 } = action;
93 OpRecord::DeleteLink {
94 original_action_hash: link_add_address.clone(),
95 base_address: base_address.clone(),
96 action: action.clone(),
97 }
98 }
99 Action::OpenChain(action) => {
100 let OpenChain {
101 prev_target,
102 close_hash,
103 ..
104 } = action;
105 OpRecord::OpenChain {
106 previous_target: prev_target.clone(),
107 close_hash: close_hash.clone(),
108 action: action.clone(),
109 }
110 }
111 Action::CloseChain(action) => {
112 let CloseChain { new_target, .. } = action;
113 OpRecord::CloseChain {
114 new_target: new_target.clone(),
115 action: action.clone(),
116 }
117 }
118 Action::Create(action) => {
119 let Create {
120 entry_type,
121 entry_hash,
122 ..
123 } = action;
124 match entry_type {
125 EntryType::AgentPubKey => OpRecord::CreateAgent {
126 agent: entry_hash.clone().into(),
127 action: action.clone(),
128 },
129 EntryType::App(entry_def) => {
130 match get_app_entry_type_for_record_authority(
131 entry_def,
132 record.entry.as_option(),
133 )? {
134 UnitEnumEither::Enum(app_entry) => OpRecord::CreateEntry {
135 app_entry,
136 action: action.clone(),
137 },
138 UnitEnumEither::Unit(app_entry_type) => {
139 OpRecord::CreatePrivateEntry {
140 app_entry_type,
141 action: action.clone(),
142 }
143 }
144 }
145 }
146 EntryType::CapClaim => OpRecord::CreateCapClaim {
147 action: action.clone(),
148 },
149 EntryType::CapGrant => OpRecord::CreateCapGrant {
150 action: action.clone(),
151 },
152 }
153 }
154 Action::Update(action) => {
155 let Update {
156 entry_type,
157 entry_hash,
158 original_action_address: original_action_hash,
159 original_entry_address: original_entry_hash,
160 ..
161 } = action;
162 match entry_type {
163 EntryType::AgentPubKey => OpRecord::UpdateAgent {
164 original_key: original_entry_hash.clone().into(),
165 original_action_hash: original_action_hash.clone(),
166 new_key: entry_hash.clone().into(),
167 action: action.clone(),
168 },
169 EntryType::App(entry_def) => {
170 match get_app_entry_type_for_record_authority(
171 entry_def,
172 record.entry.as_option(),
173 )? {
174 UnitEnumEither::Enum(app_entry) => OpRecord::UpdateEntry {
175 original_action_hash: original_action_hash.clone(),
176 original_entry_hash: original_entry_hash.clone(),
177 app_entry,
178 action: action.clone(),
179 },
180 UnitEnumEither::Unit(app_entry_type) => {
181 OpRecord::UpdatePrivateEntry {
182 original_action_hash: original_action_hash.clone(),
183 original_entry_hash: original_entry_hash.clone(),
184 app_entry_type,
185 action: action.clone(),
186 }
187 }
188 }
189 }
190 EntryType::CapClaim => OpRecord::UpdateCapClaim {
191 original_action_hash: original_action_hash.clone(),
192 original_entry_hash: original_entry_hash.clone(),
193 action: action.clone(),
194 },
195 EntryType::CapGrant => OpRecord::UpdateCapGrant {
196 original_action_hash: original_action_hash.clone(),
197 original_entry_hash: original_entry_hash.clone(),
198 action: action.clone(),
199 },
200 }
201 }
202 Action::Delete(action) => {
203 let Delete {
204 deletes_address,
205 deletes_entry_address,
206 ..
207 } = action;
208 OpRecord::DeleteEntry {
209 original_action_hash: deletes_address.clone(),
210 original_entry_hash: deletes_entry_address.clone(),
211 action: action.clone(),
212 }
213 }
214 };
215 Ok(FlatOp::StoreRecord(r))
216 }
217 Op::StoreEntry(StoreEntry { action, entry }) => {
218 let r = match &action.hashed.content {
219 EntryCreationAction::Create(action) => {
220 let Create {
221 entry_type,
222 entry_hash,
223 ..
224 } = action;
225 match entry_type {
226 EntryType::AgentPubKey => OpEntry::CreateAgent {
227 agent: entry_hash.clone().into(),
228 action: action.clone(),
229 },
230 EntryType::App(app_entry) => OpEntry::CreateEntry {
231 app_entry: get_app_entry_type_for_store_entry_authority(app_entry, entry)?,
232 action: action.clone(),
233 },
234 EntryType::CapClaim => OpEntry::CreateCapClaim {
235 entry: match entry {
236 Entry::CapClaim(entry) => entry.clone(),
237 _ => return Err(wasm_error!(WasmErrorInner::Guest(format!("Entry type does not match. CapClaim expected but got: {entry:?}"))))
238 },
239 action: action.clone(),
240 },
241 EntryType::CapGrant => OpEntry::CreateCapGrant {
242 entry: match entry {
243 Entry::CapGrant(entry) => entry.clone(),
244 _ => return Err(wasm_error!(WasmErrorInner::Guest(format!("Entry type does not match. CapGrant expected but got: {entry:?}"))))
245 },
246 action: action.clone(),
247 },
248 }
249 }
250 EntryCreationAction::Update(action) => {
251 let Update {
252 original_action_address: original_action_hash,
253 original_entry_address: original_entry_hash,
254 entry_type,
255 entry_hash,
256 ..
257 } = action;
258 match entry_type {
259 EntryType::AgentPubKey => OpEntry::UpdateAgent {
260 original_key: original_entry_hash.clone().into(),
261 original_action_hash: original_action_hash.clone(),
262 new_key: entry_hash.clone().into(),
263 action: action.clone(),
264 },
265 EntryType::App(entry_def) => {
266 let app_entry = get_app_entry_type_for_store_entry_authority(entry_def, entry)?;
267 OpEntry::UpdateEntry {
268 original_action_hash: original_action_hash.clone(),
269 original_entry_hash: original_entry_hash.clone(),
270 app_entry,
271 action: action.clone(),
272 }
273 }
274 EntryType::CapClaim => OpEntry::UpdateCapClaim {
275 original_action_hash: original_action_hash.clone(),
276 original_entry_hash: original_entry_hash.clone(),
277 entry: match entry {
278 Entry::CapClaim(entry) => entry.clone(),
279 _ => return Err(wasm_error!(WasmErrorInner::Guest(format!("Entry type does not match. CapClaim expected but got: {entry:?}"))))
280 },
281 action: action.clone(),
282 },
283 EntryType::CapGrant => OpEntry::UpdateCapGrant {
284 original_action_hash: original_action_hash.clone(),
285 original_entry_hash: original_entry_hash.clone(),
286 entry: match entry {
287 Entry::CapGrant(entry) => entry.clone(),
288 _ => return Err(wasm_error!(WasmErrorInner::Guest(format!("Entry type does not match. CapGrant expected but got: {entry:?}"))))
289 },
290 action: action.clone(),
291 },
292 }
293 }
294 };
295 Ok(FlatOp::StoreEntry(r))
296 }
297 Op::RegisterUpdate(RegisterUpdate { update, new_entry }) => {
298 let Update {
299 original_action_address: original_action_hash,
300 original_entry_address: original_entry_hash,
301 entry_type,
302 entry_hash,
303 ..
304 } = &update.hashed.content;
305 let update = match entry_type {
306 EntryType::AgentPubKey => OpUpdate::Agent {
307 original_key: original_entry_hash.clone().into(),
308 original_action_hash: original_action_hash.clone(),
309 new_key: entry_hash.clone().into(),
310 action: update.hashed.content.clone(),
311 },
312 EntryType::App(entry_def) => {
313 let new_entry = get_app_entry_type_for_record_authority::<ET>(
314 entry_def,
315 new_entry.as_ref(),
316 )?;
317 match new_entry {
318 UnitEnumEither::Enum(new) => OpUpdate::Entry {
319 app_entry: new,
320 action: update.hashed.content.clone(),
321 },
322 UnitEnumEither::Unit(new) => OpUpdate::PrivateEntry {
323 original_action_hash: original_action_hash.clone(),
324 app_entry_type: new,
325 action: update.hashed.content.clone(),
326 },
327 }
328 }
329 EntryType::CapClaim => OpUpdate::CapClaim {
330 original_action_hash: original_action_hash.clone(),
331 action: update.hashed.content.clone(),
332 },
333 EntryType::CapGrant => OpUpdate::CapGrant {
334 original_action_hash: original_action_hash.clone(),
335 action: update.hashed.content.clone(),
336 },
337 };
338 Ok(FlatOp::RegisterUpdate(update))
339 }
340 Op::RegisterAgentActivity(RegisterAgentActivity { action, .. }) => {
341 let r = match &action.hashed.content {
342 Action::Dna(action) => {
343 let Dna { hash, .. } = action;
344 OpActivity::Dna {
345 dna_hash: hash.clone(),
346 action: action.clone(),
347 }
348 }
349 Action::AgentValidationPkg(action) => {
350 let AgentValidationPkg { membrane_proof, .. } = action;
351 OpActivity::AgentValidationPkg {
352 membrane_proof: membrane_proof.clone(),
353 action: action.clone(),
354 }
355 }
356 Action::InitZomesComplete(action) => OpActivity::InitZomesComplete {
357 action: action.clone(),
358 },
359 Action::OpenChain(action) => {
360 let OpenChain {
361 prev_target,
362 close_hash,
363 ..
364 } = action;
365 OpActivity::OpenChain {
366 previous_target: prev_target.clone(),
367 close_hash: close_hash.clone(),
368 action: action.clone(),
369 }
370 }
371 Action::CloseChain(action) => {
372 let CloseChain { new_target, .. } = action;
373 OpActivity::CloseChain {
374 new_target: new_target.clone(),
375 action: action.clone(),
376 }
377 }
378 Action::CreateLink(action) => {
379 let CreateLink {
380 base_address,
381 target_address,
382 zome_index,
383 link_type,
384 tag,
385 ..
386 } = action;
387 let link_type = activity_link_type(*zome_index, *link_type)?;
388 OpActivity::CreateLink {
389 base_address: base_address.clone(),
390 target_address: target_address.clone(),
391 tag: tag.clone(),
392 link_type,
393 action: action.clone(),
394 }
395 }
396 Action::DeleteLink(action) => {
397 let DeleteLink {
398 link_add_address,
399 base_address,
400 ..
401 } = action;
402 OpActivity::DeleteLink {
403 original_action_hash: link_add_address.clone(),
404 base_address: base_address.clone(),
405 action: action.clone(),
406 }
407 }
408 Action::Create(action) => {
409 let Create {
410 entry_type,
411 entry_hash,
412 ..
413 } = action;
414 match activity_entry::<ET>(entry_type, entry_hash)? {
415 ActivityEntry::App { entry_type, .. } => OpActivity::CreateEntry {
416 app_entry_type: entry_type,
417 action: action.clone(),
418 },
419 ActivityEntry::PrivateApp { entry_type, .. } => {
420 OpActivity::CreatePrivateEntry {
421 app_entry_type: entry_type,
422 action: action.clone(),
423 }
424 }
425 ActivityEntry::Agent(agent) => OpActivity::CreateAgent {
426 agent,
427 action: action.clone(),
428 },
429 ActivityEntry::CapClaim(_hash) => OpActivity::CreateCapClaim {
430 action: action.clone(),
431 },
432 ActivityEntry::CapGrant(_hash) => OpActivity::CreateCapGrant {
433 action: action.clone(),
434 },
435 }
436 }
437 Action::Update(action) => {
438 let Update {
439 original_action_address,
440 original_entry_address,
441 entry_type,
442 entry_hash,
443 ..
444 } = action;
445 match activity_entry::<ET>(entry_type, entry_hash)? {
446 ActivityEntry::App { entry_type, .. } => OpActivity::UpdateEntry {
447 original_action_hash: original_action_address.clone(),
448 original_entry_hash: original_entry_address.clone(),
449 app_entry_type: entry_type,
450 action: action.clone(),
451 },
452 ActivityEntry::PrivateApp { entry_type, .. } => {
453 OpActivity::UpdatePrivateEntry {
454 original_action_hash: original_action_address.clone(),
455 original_entry_hash: original_entry_address.clone(),
456 app_entry_type: entry_type,
457 action: action.clone(),
458 }
459 }
460 ActivityEntry::Agent(new_key) => OpActivity::UpdateAgent {
461 original_action_hash: original_action_address.clone(),
462 original_key: original_entry_address.clone().into(),
463 new_key,
464 action: action.clone(),
465 },
466 ActivityEntry::CapClaim(_entry_hash) => OpActivity::UpdateCapClaim {
467 original_action_hash: original_action_address.clone(),
468 original_entry_hash: original_entry_address.clone(),
469 action: action.clone(),
470 },
471 ActivityEntry::CapGrant(_entry_hash) => OpActivity::UpdateCapGrant {
472 original_action_hash: original_action_address.clone(),
473 original_entry_hash: original_entry_address.clone(),
474 action: action.clone(),
475 },
476 }
477 }
478 Action::Delete(action) => {
479 let Delete {
480 deletes_address,
481 deletes_entry_address,
482 ..
483 } = action;
484 OpActivity::DeleteEntry {
485 original_action_hash: deletes_address.clone(),
486 original_entry_hash: deletes_entry_address.clone(),
487 action: action.clone(),
488 }
489 }
490 };
491 Ok(FlatOp::RegisterAgentActivity(r))
492 }
493 Op::RegisterCreateLink(RegisterCreateLink { create_link }) => {
494 let CreateLink {
495 base_address,
496 target_address,
497 zome_index,
498 link_type,
499 tag,
500 ..
501 } = &create_link.hashed.content;
502 let link_type = in_scope_link_type(*zome_index, *link_type)?;
503 Ok(FlatOp::RegisterCreateLink {
504 base_address: base_address.clone(),
505 target_address: target_address.clone(),
506 tag: tag.clone(),
507 link_type,
508 action: create_link.hashed.content.clone(),
509 })
510 }
511 Op::RegisterDeleteLink(RegisterDeleteLink {
512 delete_link,
513 create_link,
514 }) => {
515 let CreateLink {
516 base_address,
517 target_address,
518 zome_index,
519 link_type,
520 tag,
521 ..
522 } = create_link;
523 let link_type = in_scope_link_type(*zome_index, *link_type)?;
524 Ok(FlatOp::RegisterDeleteLink {
525 original_action: create_link.clone(),
526 base_address: base_address.clone(),
527 target_address: target_address.clone(),
528 tag: tag.clone(),
529 link_type,
530 action: delete_link.hashed.content.clone(),
531 })
532 }
533 Op::RegisterDelete(RegisterDelete { delete }) => Ok(FlatOp::RegisterDelete(OpDelete {
534 action: delete.hashed.content.clone(),
535 })),
536 }
537 }
538}
539
540fn get_app_entry_type_for_store_entry_authority<ET>(
543 entry_def: &AppEntryDef,
544 entry: &Entry,
545) -> Result<ET, WasmError>
546where
547 ET: EntryTypesHelper + UnitEnum,
548 <ET as UnitEnum>::Unit: Into<ZomeEntryTypesKey>,
549 WasmError: From<<ET as EntryTypesHelper>::Error>,
550{
551 let entry_type = <ET as EntryTypesHelper>::deserialize_from_type(
552 entry_def.zome_index,
553 entry_def.entry_index,
554 entry,
555 )?;
556 match entry_type {
557 Some(entry_type) => Ok(entry_type),
558 None => Err(deny_other_zome()),
559 }
560}
561
562fn get_app_entry_type_for_record_authority<ET>(
566 entry_def: &AppEntryDef,
567 entry: Option<&Entry>,
568) -> Result<UnitEnumEither<ET>, WasmError>
569where
570 ET: EntryTypesHelper + UnitEnum,
571 <ET as UnitEnum>::Unit: Into<ZomeEntryTypesKey>,
572 WasmError: From<<ET as EntryTypesHelper>::Error>,
573{
574 let AppEntryDef {
575 zome_index,
576 entry_index: entry_def_index,
577 visibility,
578 ..
579 } = entry_def;
580 match (entry, visibility) {
581 (Some(entry), EntryVisibility::Public) => {
582 get_app_entry_type_for_store_entry_authority(entry_def, entry).map(UnitEnumEither::Enum)
583 }
584
585 (None, EntryVisibility::Private) => {
586 match get_unit_entry_type::<ET>(*zome_index, *entry_def_index)? {
587 Some(unit) => Ok(UnitEnumEither::Unit(unit)),
588 None => Err(deny_other_zome()),
589 }
590 }
591
592 (Some(_), EntryVisibility::Private) => Err(wasm_error!(WasmErrorInner::Guest(format!(
593 "Entry visibility is private but an entry was provided! entry_def: {entry_def:?}"
594 )))),
595
596 (None, EntryVisibility::Public) => Err(wasm_error!(WasmErrorInner::Guest(format!(
597 "Entry visibility is public but no entry is available. entry_def: {entry_def:?}"
598 )))),
599 }
600}
601
602fn activity_entry<ET>(
606 entry_type: &EntryType,
607 entry_hash: &EntryHash,
608) -> Result<ActivityEntry<<ET as UnitEnum>::Unit>, WasmError>
609where
610 ET: UnitEnum,
611 <ET as UnitEnum>::Unit: Into<ZomeEntryTypesKey>,
612{
613 match entry_type {
614 EntryType::App(AppEntryDef {
615 zome_index,
616 entry_index: entry_def_index,
617 visibility,
618 }) => {
619 let unit = get_unit_entry_type::<ET>(*zome_index, *entry_def_index)?;
620 match visibility {
621 EntryVisibility::Public => Ok(ActivityEntry::App { entry_type: unit }),
622 EntryVisibility::Private => Ok(ActivityEntry::PrivateApp { entry_type: unit }),
623 }
624 }
625 EntryType::AgentPubKey => Ok(ActivityEntry::Agent(entry_hash.clone().into())),
626 EntryType::CapClaim => Ok(ActivityEntry::CapClaim(entry_hash.clone())),
627 EntryType::CapGrant => Ok(ActivityEntry::CapGrant(entry_hash.clone())),
628 }
629}
630
631fn in_scope_link_type<LT>(zome_index: ZomeIndex, link_type: LinkType) -> Result<LT, WasmError>
634where
635 LT: LinkTypesHelper,
636 WasmError: From<<LT as LinkTypesHelper>::Error>,
637{
638 match <LT as LinkTypesHelper>::from_type(*zome_index, *link_type)? {
639 Some(link_type) => Ok(link_type),
640 None => Err(deny_other_zome()),
641 }
642}
643
644fn activity_link_type<LT>(
647 zome_index: ZomeIndex,
648 link_type: LinkType,
649) -> Result<Option<LT>, WasmError>
650where
651 LT: LinkTypesHelper,
652 WasmError: From<<LT as LinkTypesHelper>::Error>,
653{
654 Ok(<LT as LinkTypesHelper>::from_type(*zome_index, *link_type)?)
655}
656
657fn get_unit_entry_type<ET>(
662 zome_index: ZomeIndex,
663 entry_def_index: EntryDefIndex,
664) -> Result<Option<<ET as UnitEnum>::Unit>, WasmError>
665where
666 ET: UnitEnum,
667 <ET as UnitEnum>::Unit: Into<ZomeEntryTypesKey>,
668{
669 let entries = zome_info()?.zome_types.entries;
670 let unit = entries.find(
671 <ET as UnitEnum>::unit_iter(),
672 ScopedEntryDefIndex {
673 zome_index,
674 zome_type: entry_def_index,
675 },
676 );
677 let unit = match unit {
678 Some(unit) => Some(unit),
679 None => {
680 if entries.dependencies().any(|z| z == zome_index) {
681 return Err(wasm_error!(WasmErrorInner::Guest(format!(
682 "Entry type: {entry_def_index:?} is out of range for this zome."
683 ))));
684 } else {
685 None
686 }
687 }
688 };
689 Ok(unit)
690}
691
692fn deny_other_zome() -> WasmError {
696 wasm_error!(WasmErrorInner::Host(
697 "Op called for zome it was not defined in. This is a Holochain bug".to_string()
698 ))
699}