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