1use std::{error::Error, fmt::Display, str::FromStr};
2
3use base64::Engine;
4use celestia_types::{blob::Commitment, block::Height as BlockHeight, nmt::Namespace};
5use serde::{Deserialize, Serialize};
6
7#[derive(Serialize, Deserialize, Clone)]
8pub struct BlobId {
9 pub height: BlockHeight,
10 pub namespace: Namespace,
11 pub commitment: Commitment,
12}
13
14impl BlobId {
15 pub fn new(height: BlockHeight, namespace: Namespace, commitment: Commitment) -> Self {
16 Self {
17 height,
18 namespace,
19 commitment,
20 }
21 }
22}
23
24impl std::fmt::Debug for BlobId {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 let namespace_string;
27 if let Some(namespace) = &self.namespace.id_v0() {
28 namespace_string = base64::engine::general_purpose::STANDARD.encode(namespace);
29 } else {
30 namespace_string = "Invalid v0 ID".to_string()
31 }
32 let commitment_string =
33 base64::engine::general_purpose::STANDARD.encode(&self.commitment.hash());
34 f.debug_struct("Job")
35 .field("height", &self.height.value())
36 .field("namespace", &namespace_string)
37 .field("commitment", &commitment_string)
38 .finish()
39 }
40}
41
42impl Display for BlobId {
44 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45 let namespace_string;
46 if let Some(namespace) = &self.namespace.id_v0() {
47 namespace_string = base64::engine::general_purpose::STANDARD.encode(namespace);
48 } else {
49 namespace_string = "Invalid v0 ID".to_string()
50 }
51 let commitment_string =
52 base64::engine::general_purpose::STANDARD.encode(&self.commitment.hash());
53 write!(
54 f,
55 "{}:{}:{}",
56 self.height.value(),
57 &namespace_string,
58 &commitment_string
59 )
60 }
61}
62
63impl FromStr for BlobId {
65 type Err = Box<dyn Error>;
66
67 fn from_str(s: &str) -> Result<Self, Self::Err> {
68 let mut parts = s.splitn(3, ":");
69
70 let height = BlockHeight::from_str(parts.next().ok_or("Height missing (u64)")?)?;
71
72 let n_base64 = parts
73 .next()
74 .ok_or("Namespace missing (base64)")?
75 .to_string();
76 let n_bytes = base64::engine::general_purpose::STANDARD.decode(n_base64)?;
77 let namespace = Namespace::new_v0(&n_bytes)?;
78
79 let c_base64 = parts
80 .next()
81 .ok_or("Commitment missing (base64)")?
82 .to_string();
83 let c_bytes = base64::engine::general_purpose::STANDARD.decode(c_base64)?;
84 let c_hash: [u8; 32] = c_bytes
85 .try_into()
86 .map_err(|_| "Commitment must be 32 bytes!")?;
87 let commitment = Commitment::new(c_hash.into());
88
89 Ok(Self {
90 height,
91 namespace,
92 commitment,
93 })
94 }
95}