Skip to main content

p2panda_core/operation/
operation.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2
3use std::borrow::Borrow;
4
5use crate::hash::Hash;
6use crate::identity::VerifyingKey;
7use crate::logs::SeqNum;
8use crate::operation::{AnyHeader, AnyOperation, Body, Header, HeaderError, PayloadSize};
9use crate::traits::{Chain, Digest, Extensions, Offchain, Provenance};
10
11/// Encoded bytes of an operation header and optional body.
12pub type RawOperation = (Vec<u8>, Option<Vec<u8>>);
13
14/// Combined [`Header`], [`Body`] and operation [`struct@Hash`] (Operation Id).
15#[derive(Clone, Debug)]
16pub struct Operation<E = ()> {
17    pub hash: Hash,
18    pub header: Header<E>,
19    pub body: Option<Body>,
20}
21
22impl<E> Operation<E>
23where
24    E: Extensions,
25{
26    /// Assembles an operation from it's header and optional body parts.
27    ///
28    /// Please note that this method does _not_ verify if the given body belongs to the header. Use
29    /// [`validate_operation`](crate::operation::validate_operation) if in doubt.
30    pub fn from_parts(header: Header<E>, body: Option<Body>) -> Self {
31        Self {
32            hash: header.hash(),
33            header,
34            body,
35        }
36    }
37}
38
39impl<E> PartialEq for Operation<E> {
40    fn eq(&self, other: &Self) -> bool {
41        self.hash.eq(&other.hash)
42    }
43}
44
45impl<E> Eq for Operation<E> {}
46
47impl<E> Borrow<Header<E>> for Operation<E> {
48    fn borrow(&self) -> &Header<E> {
49        &self.header
50    }
51}
52
53#[allow(clippy::non_canonical_partial_ord_impl)]
54impl<E> PartialOrd for Operation<E> {
55    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
56        Some(self.hash.cmp(&other.hash))
57    }
58}
59
60impl<E> Ord for Operation<E> {
61    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
62        self.hash.cmp(&other.hash)
63    }
64}
65
66impl<E> Digest<Hash> for Operation<E> {
67    fn hash(&self) -> Hash {
68        self.hash
69    }
70}
71
72impl<E> Provenance<VerifyingKey> for Operation<E>
73where
74    E: Extensions,
75{
76    fn author(&self) -> VerifyingKey {
77        self.header.verifying_key
78    }
79
80    fn verify(&self) -> bool {
81        self.header.verify()
82    }
83}
84
85impl<E> Chain<Hash> for Operation<E> {
86    fn backlink(&self) -> Option<Hash> {
87        self.header.backlink
88    }
89
90    fn seq_num(&self) -> SeqNum {
91        self.header.seq_num
92    }
93}
94
95impl<E> Offchain<Hash> for Operation<E> {
96    fn payload(&self) -> Option<&Body> {
97        self.body.as_ref()
98    }
99
100    fn payload_hash(&self) -> Option<Hash> {
101        self.header.payload_hash
102    }
103
104    fn payload_size(&self) -> PayloadSize {
105        self.header.payload_size
106    }
107}
108
109impl<E> TryFrom<AnyOperation> for Operation<E>
110where
111    E: Extensions,
112{
113    type Error = HeaderError;
114
115    fn try_from(any_operation: AnyOperation) -> Result<Self, Self::Error> {
116        let header: Header<E> = any_operation.header.try_into()?;
117        Ok(Operation {
118            header,
119            body: any_operation.body,
120            hash: any_operation.hash,
121        })
122    }
123}
124
125impl<E> TryFrom<(AnyHeader, Option<Body>)> for Operation<E>
126where
127    E: Extensions,
128{
129    type Error = HeaderError;
130
131    fn try_from(value: (AnyHeader, Option<Body>)) -> Result<Self, Self::Error> {
132        let (any_header, body) = value;
133
134        // Take the already computed hash from AnyHeader to save some time.
135        let hash = any_header.hash();
136
137        // Most fields have already been decoded, at this stage we only need to take the already
138        // decoded CBOR values into a Rust type representation.
139        let header: Header<E> = any_header.try_into()?;
140
141        Ok(Operation { header, body, hash })
142    }
143}