holochain_types/
action.rs

1//! Holochain's [`Action`] and its variations.
2//!
3//! All action variations contain the fields `author` and `timestamp`.
4//! Furthermore, all variations besides pub struct `Dna` (which is the first action
5//! in a chain) contain the field `prev_action`.
6
7#![allow(missing_docs)]
8
9use crate::prelude::*;
10use crate::record::RecordStatus;
11use crate::record::SignedActionHashedExt;
12use conversions::WrongActionError;
13use derive_more::From;
14use holo_hash::EntryHash;
15use holochain_zome_types::op::EntryCreationAction;
16
17#[derive(
18    Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes, Hash, derive_more::From,
19)]
20/// A action of one of the two types that create a new entry.
21pub enum NewEntryAction {
22    /// A action which simply creates a new entry
23    Create(Create),
24    /// A action which creates a new entry that is semantically related to a
25    /// previously created entry or action
26    Update(Update),
27}
28
29#[allow(missing_docs)]
30#[derive(Debug, From)]
31/// Same as NewEntryAction but takes actions as reference
32pub enum NewEntryActionRef<'a> {
33    Create(&'a Create),
34    Update(&'a Update),
35}
36
37#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes, Ord, PartialOrd)]
38/// A action of one of the two types that create a new entry.
39pub enum WireNewEntryAction {
40    Create(WireCreate),
41    Update(WireUpdate),
42}
43
44#[derive(
45    Debug, Clone, derive_more::Constructor, Serialize, Deserialize, PartialEq, Eq, Ord, PartialOrd,
46)]
47/// A action of one of the two types that create a new entry.
48pub struct WireActionStatus<W> {
49    /// Skinny action for sending over the wire.
50    pub action: W,
51    /// Validation status of this action.
52    pub validation_status: ValidationStatus,
53}
54
55/// The minimum unique data for Create actions
56/// that share a common entry
57#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes, Ord, PartialOrd)]
58pub struct WireCreate {
59    /// Timestamp is first so that deriving Ord results in
60    /// order by time
61    pub timestamp: holochain_zome_types::timestamp::Timestamp,
62    pub author: AgentPubKey,
63    pub action_seq: u32,
64    pub prev_action: ActionHash,
65    pub signature: Signature,
66    pub weight: EntryRateWeight,
67}
68
69/// The minimum unique data for Update actions
70/// that share a common entry
71#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes, Ord, PartialOrd)]
72pub struct WireUpdate {
73    /// Timestamp is first so that deriving Ord results in
74    /// order by time
75    pub timestamp: holochain_zome_types::timestamp::Timestamp,
76    pub author: AgentPubKey,
77    pub action_seq: u32,
78    pub prev_action: ActionHash,
79    pub original_entry_address: EntryHash,
80    pub original_action_address: ActionHash,
81    pub signature: Signature,
82    pub weight: EntryRateWeight,
83}
84
85/// This type is used when sending updates from the
86/// original entry authority to someone asking for
87/// metadata on that original entry.
88/// ## How updates work
89/// `Update` actions create both a new entry and
90/// a metadata relationship on the original entry.
91/// This wire data represents the metadata relationship
92/// which is stored on the original entry, i.e. this represents
93/// the "forward" reference from the original entry to the new entry.
94#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes)]
95pub struct WireUpdateRelationship {
96    /// Timestamp is first so that deriving Ord results in
97    /// order by time
98    pub timestamp: holochain_zome_types::timestamp::Timestamp,
99    pub author: AgentPubKey,
100    pub action_seq: u32,
101    pub prev_action: ActionHash,
102    /// Address of the original entry action
103    pub original_action_address: ActionHash,
104    /// The entry that this update created
105    pub new_entry_address: EntryHash,
106    /// The entry type of the entry that this action created
107    pub new_entry_type: EntryType,
108    pub signature: Signature,
109    pub weight: EntryRateWeight,
110}
111
112#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, SerializedBytes)]
113pub struct WireDelete {
114    pub delete: Delete,
115    pub signature: Signature,
116}
117
118impl NewEntryAction {
119    /// Get the entry on this action
120    pub fn entry(&self) -> &EntryHash {
121        match self {
122            NewEntryAction::Create(Create { entry_hash, .. })
123            | NewEntryAction::Update(Update { entry_hash, .. }) => entry_hash,
124        }
125    }
126
127    /// Get the entry type on this action
128    pub fn entry_type(&self) -> &EntryType {
129        match self {
130            NewEntryAction::Create(Create { entry_type, .. })
131            | NewEntryAction::Update(Update { entry_type, .. }) => entry_type,
132        }
133    }
134
135    /// Get the visibility of this action
136    pub fn visibility(&self) -> &EntryVisibility {
137        match self {
138            NewEntryAction::Create(Create { entry_type, .. })
139            | NewEntryAction::Update(Update { entry_type, .. }) => entry_type.visibility(),
140        }
141    }
142
143    /// Get the timestamp of this action
144    pub fn timestamp(&self) -> holochain_zome_types::timestamp::Timestamp {
145        match self {
146            NewEntryAction::Create(Create { timestamp, .. })
147            | NewEntryAction::Update(Update { timestamp, .. }) => *timestamp,
148        }
149    }
150
151    /// Get the author of this action
152    pub fn author(&self) -> &AgentPubKey {
153        match self {
154            NewEntryAction::Create(Create { author, .. })
155            | NewEntryAction::Update(Update { author, .. }) => author,
156        }
157    }
158
159    /// Get the action_seq of this action
160    pub fn action_seq(&self) -> u32 {
161        match self {
162            NewEntryAction::Create(Create { action_seq, .. })
163            | NewEntryAction::Update(Update { action_seq, .. }) => *action_seq,
164        }
165    }
166}
167
168impl From<NewEntryAction> for Action {
169    fn from(a: NewEntryAction) -> Self {
170        match a {
171            NewEntryAction::Create(a) => Action::Create(a),
172            NewEntryAction::Update(a) => Action::Update(a),
173        }
174    }
175}
176
177impl From<NewEntryAction> for EntryCreationAction {
178    fn from(action: NewEntryAction) -> Self {
179        match action {
180            NewEntryAction::Create(create) => EntryCreationAction::Create(create),
181            NewEntryAction::Update(update) => EntryCreationAction::Update(update),
182        }
183    }
184}
185
186impl From<(Create, Signature)> for WireCreate {
187    fn from((ec, signature): (Create, Signature)) -> Self {
188        Self {
189            timestamp: ec.timestamp,
190            author: ec.author,
191            action_seq: ec.action_seq,
192            prev_action: ec.prev_action,
193            signature,
194            weight: ec.weight,
195        }
196    }
197}
198
199impl From<(Update, Signature)> for WireUpdate {
200    fn from((eu, signature): (Update, Signature)) -> Self {
201        Self {
202            timestamp: eu.timestamp,
203            author: eu.author,
204            action_seq: eu.action_seq,
205            prev_action: eu.prev_action,
206            original_entry_address: eu.original_entry_address,
207            original_action_address: eu.original_action_address,
208            signature,
209            weight: eu.weight,
210        }
211    }
212}
213
214impl WireDelete {
215    pub fn into_record(self) -> Record {
216        Record::new(
217            SignedActionHashed::from_content_sync(SignedAction::new(
218                self.delete.into(),
219                self.signature,
220            )),
221            None,
222        )
223    }
224}
225
226impl WireUpdateRelationship {
227    /// Recreate the Update Record without an Entry.
228    /// Useful for creating dht ops
229    pub fn into_record(self, original_entry_address: EntryHash) -> Record {
230        Record::new(
231            SignedActionHashed::from_content_sync(self.into_signed_action(original_entry_address)),
232            None,
233        )
234    }
235
236    /// Render the [`SignedAction`] from the wire type
237    pub fn into_signed_action(self, original_entry_address: EntryHash) -> SignedAction {
238        let eu = Update {
239            author: self.author,
240            timestamp: self.timestamp,
241            action_seq: self.action_seq,
242            prev_action: self.prev_action,
243            original_action_address: self.original_action_address,
244            original_entry_address,
245            entry_type: self.new_entry_type,
246            entry_hash: self.new_entry_address,
247            weight: self.weight,
248        };
249        SignedAction::new(Action::Update(eu), self.signature)
250    }
251}
252
253impl NewEntryActionRef<'_> {
254    pub fn entry_type(&self) -> &EntryType {
255        match self {
256            NewEntryActionRef::Create(Create { entry_type, .. })
257            | NewEntryActionRef::Update(Update { entry_type, .. }) => entry_type,
258        }
259    }
260    pub fn entry_hash(&self) -> &EntryHash {
261        match self {
262            NewEntryActionRef::Create(Create { entry_hash, .. })
263            | NewEntryActionRef::Update(Update { entry_hash, .. }) => entry_hash,
264        }
265    }
266    pub fn to_new_entry_action(&self) -> NewEntryAction {
267        match self {
268            NewEntryActionRef::Create(create) => NewEntryAction::Create((*create).to_owned()),
269            NewEntryActionRef::Update(update) => NewEntryAction::Update((*update).to_owned()),
270        }
271    }
272}
273
274impl TryFrom<SignedActionHashed> for WireDelete {
275    type Error = WrongActionError;
276    fn try_from(sah: SignedActionHashed) -> Result<Self, Self::Error> {
277        let (a, signature) = sah.into_inner();
278        Ok(Self {
279            delete: a.into_content().try_into()?,
280            signature,
281        })
282    }
283}
284
285impl TryFrom<SignedAction> for WireDelete {
286    type Error = WrongActionError;
287    fn try_from(sa: SignedAction) -> Result<Self, Self::Error> {
288        let (a, signature) = sa.into();
289        Ok(Self {
290            delete: a.try_into()?,
291            signature,
292        })
293    }
294}
295
296impl TryFrom<SignedActionHashed> for WireUpdate {
297    type Error = WrongActionError;
298    fn try_from(sah: SignedActionHashed) -> Result<Self, Self::Error> {
299        let (a, signature) = sah.into_inner();
300        let d: Update = a.into_content().try_into()?;
301        Ok(Self {
302            signature,
303            timestamp: d.timestamp,
304            author: d.author,
305            action_seq: d.action_seq,
306            prev_action: d.prev_action,
307            original_entry_address: d.original_entry_address,
308            original_action_address: d.original_action_address,
309            weight: d.weight,
310        })
311    }
312}
313
314impl TryFrom<SignedActionHashed> for WireUpdateRelationship {
315    type Error = WrongActionError;
316    fn try_from(sah: SignedActionHashed) -> Result<Self, Self::Error> {
317        let (a, s) = sah.into_inner();
318        SignedAction::new(a.into_content(), s).try_into()
319    }
320}
321
322impl TryFrom<SignedAction> for WireUpdateRelationship {
323    type Error = WrongActionError;
324    fn try_from(sa: SignedAction) -> Result<Self, Self::Error> {
325        let (a, signature) = sa.into();
326        let d: Update = a.try_into()?;
327        Ok(Self {
328            signature,
329            timestamp: d.timestamp,
330            author: d.author,
331            action_seq: d.action_seq,
332            prev_action: d.prev_action,
333            original_action_address: d.original_action_address,
334            new_entry_address: d.entry_hash,
335            new_entry_type: d.entry_type,
336            weight: d.weight,
337        })
338    }
339}
340
341impl WireNewEntryAction {
342    pub fn into_record(self, entry_type: EntryType, entry: Entry) -> Record {
343        let entry_hash = EntryHash::with_data_sync(&entry);
344        Record::new(self.into_action(entry_type, entry_hash), Some(entry))
345    }
346
347    pub fn into_action(self, entry_type: EntryType, entry_hash: EntryHash) -> SignedActionHashed {
348        SignedActionHashed::from_content_sync(self.into_signed_action(entry_type, entry_hash))
349    }
350
351    pub fn into_signed_action(self, entry_type: EntryType, entry_hash: EntryHash) -> SignedAction {
352        match self {
353            WireNewEntryAction::Create(ec) => {
354                let signature = ec.signature;
355                let ec = Create {
356                    author: ec.author,
357                    timestamp: ec.timestamp,
358                    action_seq: ec.action_seq,
359                    prev_action: ec.prev_action,
360                    weight: ec.weight,
361                    entry_type,
362                    entry_hash,
363                };
364                SignedAction::new(ec.into(), signature)
365            }
366            WireNewEntryAction::Update(eu) => {
367                let signature = eu.signature;
368                let eu = Update {
369                    author: eu.author,
370                    timestamp: eu.timestamp,
371                    action_seq: eu.action_seq,
372                    prev_action: eu.prev_action,
373                    original_entry_address: eu.original_entry_address,
374                    original_action_address: eu.original_action_address,
375                    weight: eu.weight,
376                    entry_type,
377                    entry_hash,
378                };
379                SignedAction::new(eu.into(), signature)
380            }
381        }
382    }
383}
384
385impl WireActionStatus<WireNewEntryAction> {
386    pub fn into_record_status(self, entry_type: EntryType, entry: Entry) -> RecordStatus {
387        RecordStatus::new(
388            self.action.into_record(entry_type, entry),
389            self.validation_status,
390        )
391    }
392}
393
394impl WireActionStatus<WireUpdateRelationship> {
395    pub fn into_record_status(self, entry_hash: EntryHash) -> RecordStatus {
396        RecordStatus::new(self.action.into_record(entry_hash), self.validation_status)
397    }
398}
399
400impl WireActionStatus<WireDelete> {
401    pub fn into_record_status(self) -> RecordStatus {
402        RecordStatus::new(self.action.into_record(), self.validation_status)
403    }
404}
405
406impl<H, W, E> TryFrom<(H, ValidationStatus)> for WireActionStatus<W>
407where
408    E: Into<ActionError>,
409    H: TryInto<W, Error = E>,
410{
411    type Error = ActionError;
412
413    fn try_from(value: (H, ValidationStatus)) -> Result<Self, Self::Error> {
414        Ok(Self::new(value.0.try_into().map_err(Into::into)?, value.1))
415    }
416}
417
418impl TryFrom<SignedActionHashed> for WireNewEntryAction {
419    type Error = ActionError;
420    fn try_from(sah: SignedActionHashed) -> Result<Self, Self::Error> {
421        let action = sah.hashed.content;
422        let signature = sah.signature;
423        match action {
424            Action::Create(ec) => Ok(Self::Create((ec, signature).into())),
425            Action::Update(eu) => Ok(Self::Update((eu, signature).into())),
426            _ => Err(ActionError::NotNewEntry),
427        }
428    }
429}
430
431impl TryFrom<SignedAction> for WireNewEntryAction {
432    type Error = ActionError;
433    fn try_from(sa: SignedAction) -> Result<Self, Self::Error> {
434        let (action, s) = sa.into();
435        match action {
436            Action::Create(ec) => Ok(Self::Create((ec, s).into())),
437            Action::Update(eu) => Ok(Self::Update((eu, s).into())),
438            _ => Err(ActionError::NotNewEntry),
439        }
440    }
441}
442
443impl TryFrom<Action> for NewEntryAction {
444    type Error = WrongActionError;
445    fn try_from(value: Action) -> Result<Self, Self::Error> {
446        match value {
447            Action::Create(a) => Ok(NewEntryAction::Create(a)),
448            Action::Update(a) => Ok(NewEntryAction::Update(a)),
449            _ => Err(WrongActionError(format!("{:?}", value))),
450        }
451    }
452}
453
454impl<'a> TryFrom<&'a Action> for NewEntryActionRef<'a> {
455    type Error = WrongActionError;
456    fn try_from(value: &'a Action) -> Result<Self, Self::Error> {
457        match value {
458            Action::Create(a) => Ok(NewEntryActionRef::Create(a)),
459            Action::Update(a) => Ok(NewEntryActionRef::Update(a)),
460            _ => Err(WrongActionError(format!("{:?}", value))),
461        }
462    }
463}
464
465impl<'a> From<&'a NewEntryAction> for NewEntryActionRef<'a> {
466    fn from(n: &'a NewEntryAction) -> Self {
467        match n {
468            NewEntryAction::Create(ec) => NewEntryActionRef::Create(ec),
469            NewEntryAction::Update(eu) => NewEntryActionRef::Update(eu),
470        }
471    }
472}
473
474#[cfg(test)]
475mod tests {
476    use super::*;
477    use crate::test_utils::fake_dna_hash;
478    use crate::test_utils::fake_entry_hash;
479    use ::fixt::prelude::Unpredictable;
480
481    #[test]
482    fn test_action_msgpack_roundtrip() {
483        let orig: Action = Dna::from_builder(
484            fake_dna_hash(1),
485            ActionBuilderCommonFixturator::new(Unpredictable)
486                .next()
487                .unwrap(),
488        )
489        .into();
490        let bytes = holochain_serialized_bytes::encode(&orig).unwrap();
491        let res: Action = holochain_serialized_bytes::decode(&bytes).unwrap();
492        assert_eq!(orig, res);
493    }
494
495    #[test]
496    fn test_action_json_roundtrip() {
497        let orig: Action = Dna::from_builder(
498            fake_dna_hash(1),
499            ActionBuilderCommonFixturator::new(Unpredictable)
500                .next()
501                .unwrap(),
502        )
503        .into();
504        let orig = ActionHashed::from_content_sync(orig);
505        let json = serde_json::to_string(&orig).unwrap();
506        dbg!(&json);
507        let res: ActionHashed = serde_json::from_str(&json).unwrap();
508        assert_eq!(orig, res);
509    }
510
511    #[test]
512    fn test_create_entry_msgpack_roundtrip() {
513        let orig: Action = Create::from_builder(
514            ActionBuilderCommonFixturator::new(Unpredictable)
515                .next()
516                .unwrap(),
517            EntryType::App(AppEntryDef::new(
518                0.into(),
519                0.into(),
520                EntryVisibility::Public,
521            )),
522            fake_entry_hash(1),
523        )
524        .into();
525        let bytes = holochain_serialized_bytes::encode(&orig).unwrap();
526        println!("{:?}", bytes);
527        let res: Action = holochain_serialized_bytes::decode(&bytes).unwrap();
528        assert_eq!(orig, res);
529    }
530
531    #[test]
532    fn test_create_entry_serializedbytes_roundtrip() {
533        let orig: Action = Create::from_builder(
534            ActionBuilderCommonFixturator::new(Unpredictable)
535                .next()
536                .unwrap(),
537            EntryType::App(AppEntryDef::new(
538                0.into(),
539                0.into(),
540                EntryVisibility::Public,
541            )),
542            fake_entry_hash(1),
543        )
544        .into();
545        let bytes: SerializedBytes = orig.clone().try_into().unwrap();
546        let res: Action = bytes.try_into().unwrap();
547        assert_eq!(orig, res);
548    }
549}