p2panda_rs/operation/
encoded_operation.rs

1// SPDX-License-Identifier: AGPL-3.0-or-later
2
3use std::fmt::Display;
4use std::hash::Hash as StdHash;
5
6use serde::{Deserialize, Serialize};
7
8use crate::hash::Hash;
9use crate::serde::{deserialize_hex, serialize_hex};
10
11/// Wrapper type for operation bytes.
12///
13/// This struct can be used to deserialize an hex-encoded string into bytes when using a
14/// human-readable encoding format. No validation is applied whatsoever, except of checking if it
15/// is a valid hex-string (#OP1).
16///
17/// To validate these bytes use the `decode_operation` method to get an `PlainOperation` instance.
18/// From there you can derive a `Schema` to finally validate the operation with
19/// `validate_operation`. Read the module-level documentation for more information.
20#[derive(Clone, Debug, PartialEq, Eq, StdHash, Serialize, Deserialize)]
21pub struct EncodedOperation(
22    #[serde(serialize_with = "serialize_hex", deserialize_with = "deserialize_hex")] Vec<u8>,
23);
24
25impl EncodedOperation {
26    /// Returns new `EncodedOperation` instance from given bytes.
27    ///
28    /// This does not apply any validation and should only be used in methods where all checks have
29    /// taken place before.
30    pub fn from_bytes(bytes: &[u8]) -> Self {
31        Self(bytes.to_owned())
32    }
33
34    /// Returns the hash of this operation.
35    pub fn hash(&self) -> Hash {
36        Hash::new_from_bytes(&self.0)
37    }
38
39    /// Returns operation as bytes.
40    pub fn into_bytes(&self) -> Vec<u8> {
41        self.0.clone()
42    }
43
44    /// Returns payload size (number of bytes) of encoded operation.
45    pub fn size(&self) -> u64 {
46        self.0.len() as u64
47    }
48}
49
50impl Display for EncodedOperation {
51    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
52        write!(f, "{}", hex::encode(&self.0))
53    }
54}
55
56#[cfg(any(feature = "test-utils", test))]
57impl EncodedOperation {
58    /// Returns a new instance of `EncodedOperation` for testing.
59    pub fn new(bytes: &[u8]) -> EncodedOperation {
60        Self(bytes.to_owned())
61    }
62
63    /// Converts hexadecimal string into bytes and returns as a new instance of `EncodedOperation`.
64    pub fn from_hex(value: &str) -> EncodedOperation {
65        let bytes = hex::decode(value).expect("invalid hexadecimal value");
66        Self(bytes)
67    }
68}
69
70#[cfg(test)]
71mod tests {
72    use std::collections::HashMap;
73
74    use ciborium::cbor;
75    use rstest::rstest;
76
77    use crate::operation::EncodedOperation;
78    use crate::serde::serialize_value;
79    use crate::test_utils::fixtures::encoded_operation;
80
81    #[test]
82    fn byte_and_str_representation() {
83        let bytes = serialize_value(cbor!([
84            1,
85            2,
86            "schema_field_definition_v1",
87            ["00200f64117685c68c82154fb87260e670884a410a4add69c33bf4f606297b83b6ca"]
88        ]));
89
90        let encoded_operation = EncodedOperation::from_bytes(&bytes);
91
92        // Return bytes and size
93        assert_eq!(encoded_operation.into_bytes(), bytes);
94        assert_eq!(encoded_operation.size() as usize, bytes.len());
95
96        // Represent bytes as hexadecimal string
97        assert_eq!(
98            concat!(
99                "840102781a736368656d615f6669656c645f646566696e69746",
100                "96f6e5f76318178443030323030663634313137363835633638",
101                "633832313534666238373236306536373038383461343130613",
102                "461646436396333336266346636303632393762383362366361"
103            ),
104            format!("{}", encoded_operation)
105        );
106    }
107
108    #[rstest]
109    fn it_hashes(encoded_operation: EncodedOperation) {
110        // Use operation as key in hash map
111        let mut hash_map = HashMap::new();
112        let key_value = "Value identified by a hash".to_string();
113        hash_map.insert(&encoded_operation, key_value.clone());
114
115        // Retrieve value from hash map via key
116        let key_value_retrieved = hash_map.get(&encoded_operation).unwrap().to_owned();
117        assert_eq!(key_value, key_value_retrieved)
118    }
119}