p2panda_rs/operation/
operation_id.rs1use std::fmt::Display;
4use std::str::FromStr;
5
6use serde::{Deserialize, Serialize};
7
8use crate::hash::{Hash, HashId};
9use crate::operation::error::OperationIdError;
10use crate::{Human, Validate};
11
12#[derive(Clone, Debug, Eq, Hash, Ord, PartialOrd, PartialEq, Serialize, Deserialize)]
17pub struct OperationId(Hash);
18
19impl OperationId {
20 pub fn new(entry_hash: &Hash) -> Self {
22 Self(entry_hash.to_owned())
23 }
24
25 pub fn as_str(&self) -> &str {
27 self.0.as_str()
28 }
29}
30
31impl HashId for OperationId {
32 fn as_hash(&self) -> &Hash {
34 &self.0
35 }
36}
37
38impl Validate for OperationId {
39 type Error = OperationIdError;
40
41 fn validate(&self) -> Result<(), Self::Error> {
42 self.0.validate()?;
43 Ok(())
44 }
45}
46
47impl From<Hash> for OperationId {
48 fn from(hash: Hash) -> Self {
49 Self::new(&hash)
50 }
51}
52
53impl FromStr for OperationId {
54 type Err = OperationIdError;
55
56 fn from_str(s: &str) -> Result<Self, Self::Err> {
57 Ok(Self(Hash::new(s)?))
58 }
59}
60
61impl Display for OperationId {
62 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
63 write!(f, "{}", self.as_str())
64 }
65}
66
67impl Human for OperationId {
68 fn display(&self) -> String {
69 let offset = yasmf_hash::MAX_YAMF_HASH_SIZE * 2 - 6;
70 format!("<Operation {}>", &self.0.as_str()[offset..])
71 }
72}
73
74#[cfg(test)]
75mod tests {
76 use std::str::FromStr;
77
78 use ciborium::cbor;
79 use rstest::rstest;
80
81 use crate::hash::Hash;
82 use crate::serde::{deserialize_into, hex_string_to_bytes, serialize_from, serialize_value};
83 use crate::test_utils::fixtures::random_hash;
84 use crate::Human;
85
86 use super::OperationId;
87
88 #[test]
89 fn from_str() {
90 let hash_str = "0020cfb0fa37f36d082faad3886a9ffbcc2813b7afe90f0609a556d425f1a76ec805";
92 let operation_id: OperationId = hash_str.parse().unwrap();
93 assert_eq!(
94 operation_id,
95 OperationId::new(&Hash::new(hash_str).unwrap())
96 );
97
98 assert!("This is not a hash".parse::<OperationId>().is_err());
100 }
101
102 #[rstest]
103 fn from_hash(#[from(random_hash)] hash: Hash) {
104 let operation_id = OperationId::from(hash.clone());
106 assert_eq!(operation_id, OperationId::new(&hash));
107 }
108
109 #[test]
110 fn string_representation() {
111 let hash_str = "0020cfb0fa37f36d082faad3886a9ffbcc2813b7afe90f0609a556d425f1a76ec805";
112 let operation_id = OperationId::new(&Hash::new(hash_str).unwrap());
113
114 assert_eq!(operation_id.as_str(), hash_str);
115 assert_eq!(operation_id.to_string(), hash_str);
116 assert_eq!(format!("{}", operation_id), hash_str);
117 }
118
119 #[test]
120 fn short_representation() {
121 let hash_str = "0020cfb0fa37f36d082faad3886a9ffbcc2813b7afe90f0609a556d425f1a76ec805";
122 let operation_id = OperationId::new(&Hash::new(hash_str).unwrap());
123
124 assert_eq!(operation_id.display(), "<Operation 6ec805>");
125 }
126
127 #[test]
128 fn serialize() {
129 let bytes = serialize_from(
130 OperationId::from_str(
131 "0020cfb0fa37f36d082faad3886a9ffbcc2813b7afe90f0609a556d425f1a76ec805",
132 )
133 .unwrap(),
134 );
135
136 assert_eq!(
137 bytes,
138 vec![
139 88, 34, 0, 32, 207, 176, 250, 55, 243, 109, 8, 47, 170, 211, 136, 106, 159, 251,
140 204, 40, 19, 183, 175, 233, 15, 6, 9, 165, 86, 212, 37, 241, 167, 110, 200, 5
141 ]
142 );
143 }
144
145 #[test]
146 fn deserialize() {
147 let hash_str = "0020cfb0fa37f36d082faad3886a9ffbcc2813b7afe90f0609a556d425f1a76ec805";
148 let operation_id: OperationId =
149 deserialize_into(&serialize_value(cbor!(hex_string_to_bytes(
150 "0020cfb0fa37f36d082faad3886a9ffbcc2813b7afe90f0609a556d425f1a76ec805"
151 ))))
152 .unwrap();
153 assert_eq!(OperationId::from_str(hash_str).unwrap(), operation_id);
154
155 let invalid_hash = deserialize_into::<OperationId>(&serialize_value(cbor!("1234")));
157 assert!(invalid_hash.is_err());
158 let empty_hash = deserialize_into::<OperationId>(&serialize_value(cbor!("")));
159 assert!(empty_hash.is_err());
160 }
161}