1use bc_components::XID;
2use bc_envelope::prelude::*;
3
4use crate::{Error, Result};
5
6#[derive(Clone, PartialEq, Eq, Hash)]
7pub struct Receipt(Digest);
8
9pub const RECEIPT_TYPE: &str = "Receipt";
10
11impl Receipt {
12 pub fn new(user_id: XID, data: impl AsRef<[u8]>) -> Self {
13 Self(Digest::from_image_parts(&[user_id.data(), data.as_ref()]))
14 }
15}
16
17impl std::ops::Deref for Receipt {
18 type Target = Digest;
19
20 fn deref(&self) -> &Self::Target { &self.0 }
21}
22
23impl std::fmt::Debug for Receipt {
24 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
25 write!(f, "Receipt({})", hex::encode(&self.0))
26 }
27}
28
29impl From<Receipt> for Envelope {
30 fn from(receipt: Receipt) -> Self {
31 Envelope::new(CBOR::to_byte_string(receipt.0.clone()))
32 .add_type(RECEIPT_TYPE)
33 }
34}
35
36impl TryFrom<Envelope> for Receipt {
37 type Error = Error;
38
39 fn try_from(envelope: Envelope) -> Result<Self> {
40 envelope.check_type_envelope(RECEIPT_TYPE).map_err(|e| {
41 Error::TypeMismatch {
42 expected: RECEIPT_TYPE.to_string(),
43 found: format!("envelope without type or wrong type: {}", e),
44 }
45 })?;
46 let bytes: ByteString =
47 envelope
48 .extract_subject()
49 .map_err(|e| Error::InvalidEnvelope {
50 message: format!(
51 "failed to extract subject as bytes: {}",
52 e
53 ),
54 })?;
55 let digest = Digest::from_data_ref(bytes.data()).map_err(|e| {
56 Error::InvalidDigest {
57 message: format!("failed to create digest from bytes: {}", e),
58 }
59 })?;
60 Ok(Self(digest))
61 }
62}
63
64impl From<&Receipt> for Receipt {
65 fn from(receipt: &Receipt) -> Self { receipt.clone() }
66}
67
68#[cfg(test)]
69mod tests {
70 use hex_literal::hex;
71 use indoc::indoc;
72
73 use super::*;
74
75 #[test]
76 fn test_receipt() {
77 let user_id = XID::from_data_ref(hex!(
78 "3eadf5bf7a4da69f824be029d2d0ece06fcb3aca7dd85d402b661f7b48f18294"
79 ))
80 .unwrap();
81 let receipt = Receipt::new(user_id, b"data");
82 assert_eq!(
83 format!("{:?}", receipt),
84 "Receipt(12bd077763220d3223f6cd74f4d51103f29c7ba70b68765cd8ee13c84ee50152)"
85 );
86
87 let envelope = receipt.clone().to_envelope();
88 assert_eq!(
89 format!("{}", envelope.ur_string()),
90 "ur:envelope/lftpsohdcxbgryatktiacpbteycnynsnjywktlbyaxwznskgosbdiskohhtpwybwspglvwadgmoyadtpsoiogmihiaihinjojyamdwplrf"
91 );
92 #[rustfmt::skip]
93 assert_eq!(envelope.format(), indoc!{r#"
94 Bytes(32) [
95 'isA': "Receipt"
96 ]
97 "#}.trim());
98
99 let receipt_2 = Receipt::try_from(envelope).unwrap();
100 assert_eq!(receipt, receipt_2);
101 }
102}