1use std::convert::TryFrom;
4use std::fmt::{self, Display, Write};
5use std::hash::Hash as StdHash;
6use std::slice::Iter;
7use std::str::FromStr;
8
9use serde::de::Visitor;
10use serde::{Deserialize, Deserializer, Serialize};
11
12use crate::document::error::DocumentViewIdError;
13use crate::hash::{Hash, HashId};
14use crate::operation::error::OperationIdError;
15use crate::operation::OperationId;
16use crate::{Human, Validate};
17
18#[derive(Clone, Debug, Ord, PartialOrd, Eq, PartialEq, StdHash, Serialize)]
44pub struct DocumentViewId(Vec<OperationId>);
45
46impl DocumentViewId {
47 pub fn new(graph_tips: &[OperationId]) -> Self {
51 let mut graph_tips = graph_tips.to_owned();
52
53 graph_tips.sort();
55
56 graph_tips.dedup();
58
59 Self(graph_tips)
60 }
61
62 pub(crate) fn from_untrusted(
70 graph_tips: Vec<OperationId>,
71 ) -> Result<Self, DocumentViewIdError> {
72 let document_view_id = Self(graph_tips);
74
75 document_view_id.validate()?;
77
78 Ok(document_view_id)
79 }
80
81 pub fn graph_tips(&self) -> &[OperationId] {
83 self.0.as_slice()
84 }
85
86 pub fn iter(&self) -> Iter<OperationId> {
88 self.graph_tips().iter()
89 }
90}
91
92impl Validate for DocumentViewId {
93 type Error = DocumentViewIdError;
94
95 fn validate(&self) -> Result<(), Self::Error> {
100 if self.0.is_empty() {
102 return Err(DocumentViewIdError::ZeroOperationIds);
103 };
104
105 let mut prev_operation_id: Option<&OperationId> = None;
106
107 for operation_id in &self.0 {
108 operation_id.validate()?;
110
111 if let Some(prev) = prev_operation_id {
113 if prev >= operation_id {
114 return Err(DocumentViewIdError::UnsortedOperationIds);
115 }
116 }
117
118 prev_operation_id = Some(operation_id);
119 }
120
121 Ok(())
122 }
123}
124
125impl Display for DocumentViewId {
126 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127 for (i, operation_id) in self.iter().enumerate() {
128 let separator = if i == 0 { "" } else { "_" };
129 let _ = write!(f, "{}{}", &separator, operation_id.as_str());
130 }
131
132 Ok(())
133 }
134}
135
136impl Human for DocumentViewId {
137 fn display(&self) -> String {
138 let mut result = String::new();
139 let offset = yasmf_hash::MAX_YAMF_HASH_SIZE * 2 - 6;
140
141 for (i, operation_id) in self.iter().enumerate() {
142 let separator = if i == 0 { "" } else { "_" };
143 write!(result, "{}{}", &separator, &operation_id.as_str()[offset..]).unwrap();
144 }
145
146 result
147 }
148}
149
150impl<'de> Deserialize<'de> for DocumentViewId {
151 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
152 where
153 D: Deserializer<'de>,
154 {
155 struct DocumentViewIdVisitor;
156
157 impl<'de> Visitor<'de> for DocumentViewIdVisitor {
158 type Value = DocumentViewId;
159
160 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
161 formatter.write_str("document view id as array or in string representation")
162 }
163
164 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
168 where
169 E: serde::de::Error,
170 {
171 DocumentViewId::from_str(value).map_err(serde::de::Error::custom)
172 }
173
174 fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
176 where
177 A: serde::de::SeqAccess<'de>,
178 {
179 let mut operation_ids = Vec::new();
180
181 while let Some(operation_id) = seq.next_element::<OperationId>()? {
182 operation_ids.push(operation_id);
183 }
184
185 let view_id = DocumentViewId::from_untrusted(operation_ids)
186 .map_err(serde::de::Error::custom)?;
187
188 Ok(view_id)
189 }
190 }
191
192 let view_id = deserializer.deserialize_any(DocumentViewIdVisitor)?;
193
194 Ok(view_id)
195 }
196}
197
198impl TryFrom<&[String]> for DocumentViewId {
199 type Error = DocumentViewIdError;
200
201 fn try_from(str_list: &[String]) -> Result<Self, Self::Error> {
202 let operation_ids: Result<Vec<OperationId>, OperationIdError> = str_list
203 .iter()
204 .map(|operation_id_str| operation_id_str.parse::<OperationId>())
205 .collect();
206
207 Self::from_untrusted(operation_ids?)
208 }
209}
210
211impl<T: HashId> TryFrom<&[T]> for DocumentViewId {
212 type Error = DocumentViewIdError;
213
214 fn try_from(str_list: &[T]) -> Result<Self, Self::Error> {
215 let operation_ids: Vec<OperationId> = str_list
216 .iter()
217 .map(|hash_id| hash_id.as_hash().clone().into())
218 .collect();
219
220 Self::from_untrusted(operation_ids)
221 }
222}
223
224impl TryFrom<&[Hash]> for DocumentViewId {
225 type Error = DocumentViewIdError;
226
227 fn try_from(str_list: &[Hash]) -> Result<Self, Self::Error> {
228 let operation_ids: Vec<OperationId> =
229 str_list.iter().map(|hash| hash.clone().into()).collect();
230
231 Self::from_untrusted(operation_ids)
232 }
233}
234
235impl From<DocumentViewId> for Vec<Hash> {
236 fn from(value: DocumentViewId) -> Self {
237 value.iter().map(HashId::as_hash).cloned().collect()
238 }
239}
240
241impl From<OperationId> for DocumentViewId {
246 fn from(operation_id: OperationId) -> Self {
247 Self(vec![operation_id])
248 }
249}
250
251impl From<Hash> for DocumentViewId {
256 fn from(hash: Hash) -> Self {
257 Self(vec![hash.into()])
258 }
259}
260
261impl FromStr for DocumentViewId {
266 type Err = DocumentViewIdError;
267
268 fn from_str(s: &str) -> Result<Self, Self::Err> {
269 let mut operations: Vec<OperationId> = Vec::new();
270
271 s.split('_')
272 .try_for_each::<_, Result<(), Self::Err>>(|hash_str| {
273 let operation_id = OperationId::from_str(hash_str)?;
274 operations.push(operation_id);
275 Ok(())
276 })?;
277
278 Self::from_untrusted(operations)
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use std::collections::hash_map::DefaultHasher;
285 use std::hash::{Hash as StdHash, Hasher};
286 use std::str::FromStr;
287
288 use rstest::rstest;
289 use serde::{Deserialize, Serialize};
290
291 use crate::hash::Hash;
292 use crate::operation::OperationId;
293 use crate::serde::{hex_string_to_bytes, serialize_from};
294 use crate::test_utils::constants::HASH;
295 use crate::test_utils::fixtures::random_hash;
296 use crate::test_utils::fixtures::{document_view_id, random_operation_id};
297 use crate::{Human, Validate};
298
299 use super::DocumentViewId;
300
301 #[rstest]
302 fn constructor_converts_to_canonic_format() {
303 let operation_id_1: OperationId =
304 "00201413ae916e6745ab715c1f5ab49c47d6773c3c0febd970ecf1039beed203b472"
305 .parse()
306 .unwrap();
307 let operation_id_2: OperationId =
308 "0020266fe901ea7d3efa983f12d145089d29480064b0da7393a8c0779af7488c7f0d"
309 .parse()
310 .unwrap();
311 let operation_id_3: OperationId =
312 "0020387b96cfdc7ac155eff0a9941400dee4a21e7cf18dcccefbf0a46a7c0138bbf5"
313 .parse()
314 .unwrap();
315 let operation_id_4: OperationId =
316 "002047e8d17a2edb41621beec8c710ee71a1b2ea81d356f05cd466526b269a7b2493"
317 .parse()
318 .unwrap();
319
320 let document_view_id_1 = DocumentViewId::new(&[
322 operation_id_1.clone(),
323 operation_id_2.clone(),
324 operation_id_3.clone(),
325 operation_id_4.clone(),
326 ]);
327 assert!(document_view_id_1.validate().is_ok());
328
329 let document_view_id_2 = DocumentViewId::new(&[
331 operation_id_3.clone(),
332 operation_id_3.clone(),
333 operation_id_2.clone(),
334 operation_id_4.clone(),
335 operation_id_1.clone(),
336 operation_id_4.clone(),
337 ]);
338 assert!(document_view_id_2.validate().is_ok());
339 assert_eq!(document_view_id_2.graph_tips().len(), 4);
340
341 assert!(DocumentViewId::from_untrusted(vec![
342 operation_id_3.clone(),
343 operation_id_3,
344 operation_id_2,
345 operation_id_4.clone(),
346 operation_id_1,
347 operation_id_4,
348 ])
349 .is_err());
350 }
351
352 #[rstest]
353 fn conversion(#[from(random_hash)] hash: Hash) {
354 let hash_str = "0020d3235c8fe6f58608200851b83cd8482808eb81e4c6b4b17805bba57da9f16e79";
356 let document_id: DocumentViewId = hash_str.parse().unwrap();
357 assert_eq!(
358 document_id,
359 DocumentViewId::new(&[hash_str.parse::<OperationId>().unwrap()])
360 );
361
362 let document_id: DocumentViewId = hash.clone().into();
364 assert_eq!(document_id, DocumentViewId::new(&[hash.clone().into()]));
365
366 let document_id: DocumentViewId = OperationId::new(&hash).into();
368 assert_eq!(document_id, DocumentViewId::new(&[hash.into()]));
369
370 assert!("This is not a hash".parse::<DocumentViewId>().is_err());
372 }
373
374 #[rstest]
375 fn iterates(document_view_id: DocumentViewId) {
376 for hash in document_view_id.graph_tips() {
377 assert!(hash.validate().is_ok());
378 }
379 }
380
381 #[test]
382 fn string_representation() {
383 let document_view_id = HASH.parse::<DocumentViewId>().unwrap();
384
385 assert_eq!(
386 document_view_id.to_string(),
387 "0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543"
388 );
389
390 assert_eq!(
391 format!("{}", document_view_id),
392 "0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543"
393 );
394
395 let operation_1 = "0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543"
396 .parse::<OperationId>()
397 .unwrap();
398 let operation_2 = "0020d3235c8fe6f58608200851b83cd8482808eb81e4c6b4b17805bba57da9f16e79"
399 .parse::<OperationId>()
400 .unwrap();
401
402 let document_view_id = DocumentViewId::new(&[operation_1, operation_2]);
403 assert_eq!(document_view_id.to_string(), "0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543_0020d3235c8fe6f58608200851b83cd8482808eb81e4c6b4b17805bba57da9f16e79");
404 assert_eq!("0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543_0020d3235c8fe6f58608200851b83cd8482808eb81e4c6b4b17805bba57da9f16e79".parse::<DocumentViewId>().unwrap(), document_view_id);
405 }
406
407 #[test]
408 fn short_representation() {
409 let operation_1 = "0020b177ec1bf26dfb3b7010d473e6d44713b29b765b99c6e60ecbfae742de496543"
410 .parse::<OperationId>()
411 .unwrap();
412 let operation_2 = "0020d3235c8fe6f58608200851b83cd8482808eb81e4c6b4b17805bba57da9f16e79"
413 .parse::<OperationId>()
414 .unwrap();
415
416 let view_id_unmerged = DocumentViewId::new(&[operation_1, operation_2]);
417 assert_eq!(view_id_unmerged.display(), "496543_f16e79");
418 }
419
420 #[rstest]
421 fn equality(
422 #[from(random_operation_id)] operation_id_1: OperationId,
423 #[from(random_operation_id)] operation_id_2: OperationId,
424 ) {
425 let view_id_1 = DocumentViewId::new(&[operation_id_1.clone(), operation_id_2.clone()]);
426 let view_id_2 = DocumentViewId::new(&[operation_id_2, operation_id_1]);
427 assert_eq!(view_id_1, view_id_2);
428 }
429
430 #[rstest]
431 fn hash_equality(
432 #[from(random_operation_id)] operation_id_1: OperationId,
433 #[from(random_operation_id)] operation_id_2: OperationId,
434 ) {
435 let mut hasher_1 = DefaultHasher::default();
436 let mut hasher_2 = DefaultHasher::default();
437 let view_id_1 = DocumentViewId::new(&[operation_id_1.clone(), operation_id_2.clone()]);
438 let view_id_2 = DocumentViewId::new(&[operation_id_2, operation_id_1]);
439 view_id_1.hash(&mut hasher_1);
440 view_id_2.hash(&mut hasher_2);
441 assert_eq!(hasher_1.finish(), hasher_2.finish());
442 }
443
444 #[rstest]
445 fn deserialize_unsorted_view_id(
446 #[from(random_operation_id)] operation_id_1: OperationId,
447 #[from(random_operation_id)] operation_id_2: OperationId,
448 ) {
449 let unsorted_hashes = [
451 hex_string_to_bytes(
452 "0020c13cdc58dfc6f4ebd32992ff089db79980363144bdb2743693a019636fa72ec8",
453 ),
454 hex_string_to_bytes(
455 "00202dce4b32cd35d61cf54634b93a526df333c5ed3d93230c2f026f8d1ecabc0cd7",
456 ),
457 ];
458 let mut cbor_bytes = Vec::new();
459 ciborium::ser::into_writer(&unsorted_hashes, &mut cbor_bytes).unwrap();
460
461 let result: Result<DocumentViewId, ciborium::de::Error<std::io::Error>> =
463 ciborium::de::from_reader(&cbor_bytes[..]);
464
465 let expected_result = ciborium::de::Error::<std::io::Error>::Semantic(
466 None,
467 "expected sorted operation ids in document view id".to_string(),
468 );
469
470 assert_eq!(result.unwrap_err().to_string(), expected_result.to_string());
471
472 let mut reversed_ids = vec![operation_id_1, operation_id_2];
474 reversed_ids.sort();
475 reversed_ids.reverse();
476 let view_id_unsorted = DocumentViewId::new(&reversed_ids);
477
478 let mut cbor_bytes = Vec::new();
479 ciborium::ser::into_writer(&view_id_unsorted, &mut cbor_bytes).unwrap();
480
481 let result: Result<DocumentViewId, ciborium::de::Error<std::io::Error>> =
482 ciborium::de::from_reader(&cbor_bytes[..]);
483 assert!(result.is_ok());
484 assert_eq!(result.unwrap(), view_id_unsorted);
485 }
486
487 #[test]
488 fn deserialize_invalid_view_id() {
489 let invalid_hashes = [
491 hex_string_to_bytes(
492 "0020c13cdc58dfc6f4ebd32992ff089db79980363144bdb2743693a019636fa72ec8",
493 ),
494 hex_string_to_bytes("2dce4b32cd35d61cf54634b93a526df333c5ed3d93230c2f026f8d1ecabc0cd7"),
495 ];
496 let mut cbor_bytes = Vec::new();
497 ciborium::ser::into_writer(&invalid_hashes, &mut cbor_bytes).unwrap();
498 let invalid_id_encoded = hex::encode(cbor_bytes);
499
500 let result: Result<DocumentViewId, ciborium::de::Error<std::io::Error>> =
502 ciborium::de::from_reader(&hex::decode(invalid_id_encoded).unwrap()[..]);
503
504 let expected_result = ciborium::de::Error::<std::io::Error>::Semantic(
505 None,
506 "invalid hash length 32 bytes, expected 34 bytes".to_string(),
507 );
508
509 assert_eq!(result.unwrap_err().to_string(), expected_result.to_string());
510 }
511
512 #[test]
513 fn deserialize_human_readable() {
514 let hash_str = "0020cfb0fa37f36d082faad3886a9ffbcc2813b7afe90f0609a556d425f1a76ec805_0020cfb0fa37f36d082faad3886a9ffbcc2813b7afe90f0609a556d425f1a76ec808";
515
516 #[derive(Deserialize, Serialize, Debug, PartialEq)]
517 struct Test {
518 document_view_id: DocumentViewId,
519 }
520
521 let json = format!(
523 r#"
524 {{
525 "document_view_id": "{hash_str}"
526 }}
527 "#
528 );
529
530 let result: Test = serde_json::from_str(&json).unwrap();
531 assert_eq!(
532 Test {
533 document_view_id: DocumentViewId::from_str(hash_str).unwrap(),
534 },
535 result
536 );
537
538 let bytes = serialize_from(result);
540 assert_eq!(
541 bytes,
542 [
543 161, 112, 100, 111, 99, 117, 109, 101, 110, 116, 95, 118, 105, 101, 119, 95, 105,
547 100, 130, 88, 34, 0, 32, 207, 176, 250, 55, 243, 109, 8, 47, 170, 211, 136, 106,
548 159, 251, 204, 40, 19, 183, 175, 233, 15, 6, 9, 165, 86, 212, 37, 241, 167, 110,
549 200, 5, 88, 34, 0, 32, 207, 176, 250, 55, 243, 109, 8, 47, 170, 211, 136, 106, 159,
550 251, 204, 40, 19, 183, 175, 233, 15, 6, 9, 165, 86, 212, 37, 241, 167, 110, 200, 8
551 ]
552 )
553 }
554}