drawbridge_type/digest/
digests.rs1use super::{Algorithm, Error, Reader, Verifier, Writer};
4
5use std::collections::btree_map::IntoIter;
6use std::collections::BTreeMap;
7use std::ops::{Deref, DerefMut};
8use std::str::FromStr;
9
10use drawbridge_byte::Bytes;
11use serde::{Deserialize, Serialize};
12
13#[cfg(feature = "headers")]
14use headers::{Error as HeadErr, Header, HeaderName, HeaderValue};
15
16#[derive(Clone, Default, Debug, Serialize, Deserialize)]
18pub struct ContentDigest<H = Box<[u8]>>(BTreeMap<Algorithm, Bytes<H>>)
19where
20 H: AsRef<[u8]> + From<Vec<u8>>;
21
22impl<H> ContentDigest<H>
23where
24 H: AsRef<[u8]> + From<Vec<u8>>,
25{
26 pub fn reader<T>(&self, reader: T) -> Reader<T> {
28 Reader::new(reader, self.iter().map(|x| *x.0))
29 }
30
31 pub fn writer<T>(&self, writer: T) -> Writer<T> {
33 Writer::new(writer, self.iter().map(|x| *x.0))
34 }
35
36 pub fn verifier<T>(self, reader: T) -> Verifier<T, H> {
38 Verifier::new(self.reader(reader), self)
39 }
40}
41
42impl<H> From<BTreeMap<Algorithm, Bytes<H>>> for ContentDigest<H>
43where
44 H: AsRef<[u8]> + From<Vec<u8>>,
45{
46 fn from(value: BTreeMap<Algorithm, Bytes<H>>) -> Self {
47 Self(value)
48 }
49}
50
51impl<H> Eq for ContentDigest<H> where H: AsRef<[u8]> + From<Vec<u8>> {}
52impl<T, U> PartialEq<ContentDigest<U>> for ContentDigest<T>
53where
54 T: AsRef<[u8]> + From<Vec<u8>>,
55 U: AsRef<[u8]> + From<Vec<u8>>,
56{
57 fn eq(&self, other: &ContentDigest<U>) -> bool {
58 if self.len() != other.len() {
59 return false;
60 }
61
62 for (lhs, rhs) in self.0.iter().zip(other.0.iter()) {
63 if lhs.0 != rhs.0 {
64 return false;
65 }
66
67 if lhs.1.as_ref() != rhs.1.as_ref() {
68 return false;
69 }
70 }
71
72 true
73 }
74}
75
76impl<H> Deref for ContentDigest<H>
77where
78 H: AsRef<[u8]> + From<Vec<u8>>,
79{
80 type Target = BTreeMap<Algorithm, Bytes<H>>;
81
82 fn deref(&self) -> &Self::Target {
83 &self.0
84 }
85}
86
87impl<H> DerefMut for ContentDigest<H>
88where
89 H: AsRef<[u8]> + From<Vec<u8>>,
90{
91 fn deref_mut(&mut self) -> &mut Self::Target {
92 &mut self.0
93 }
94}
95
96impl<H> std::fmt::Display for ContentDigest<H>
97where
98 H: AsRef<[u8]> + From<Vec<u8>>,
99{
100 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
101 let mut comma = "";
102
103 for (algo, hash) in self.iter() {
104 write!(f, "{comma}{algo}=:{hash}:")?;
105 comma = ",";
106 }
107
108 Ok(())
109 }
110}
111
112impl<H> FromStr for ContentDigest<H>
113where
114 H: AsRef<[u8]> + From<Vec<u8>>,
115{
116 type Err = Error;
117
118 fn from_str(s: &str) -> Result<Self, Self::Err> {
119 s.split(',')
120 .map(|s| {
121 let (key, val) = s.split_once('=').ok_or(Error::MissingEq)?;
122 let alg = key.parse()?;
123 let b64 = val
124 .strip_prefix(':')
125 .and_then(|val| val.strip_suffix(':'))
126 .ok_or(Error::MissingColons)?
127 .parse()?;
128 Ok((alg, b64))
129 })
130 .collect::<Result<_, _>>()
131 .map(Self)
132 }
133}
134
135impl<H> IntoIterator for ContentDigest<H>
136where
137 H: AsRef<[u8]> + From<Vec<u8>>,
138{
139 type Item = (Algorithm, Bytes<H>);
140 type IntoIter = IntoIter<Algorithm, Bytes<H>>;
141
142 fn into_iter(self) -> Self::IntoIter {
143 self.0.into_iter()
144 }
145}
146
147#[cfg(feature = "headers")]
148static CONTENT_DIGEST: HeaderName = HeaderName::from_static("content-digest");
149
150#[cfg(feature = "headers")]
151impl<H> Header for ContentDigest<H>
152where
153 H: Default + AsRef<[u8]> + From<Vec<u8>>,
154{
155 fn name() -> &'static HeaderName {
156 &CONTENT_DIGEST
157 }
158
159 #[allow(single_use_lifetimes)]
160 fn decode<'i, I>(values: &mut I) -> Result<Self, HeadErr>
161 where
162 Self: Sized,
163 I: Iterator<Item = &'i HeaderValue>,
164 {
165 let mut all = Self::default();
166
167 for value in values {
168 let digests: ContentDigest<H> = std::str::from_utf8(value.as_bytes())
169 .map_err(|_| HeadErr::invalid())?
170 .parse()
171 .map_err(|_| HeadErr::invalid())?;
172
173 for (algo, hash) in digests {
174 let _ = all.insert(algo, hash);
175 }
176 }
177
178 if all.is_empty() {
179 return Err(HeadErr::invalid());
180 }
181
182 Ok(all)
183 }
184
185 fn encode<E: Extend<HeaderValue>>(&self, values: &mut E) {
186 let value = HeaderValue::from_str(&self.to_string()).unwrap();
187 values.extend([value])
188 }
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194
195 #[async_std::test]
196 async fn isomorphism() {
197 const STR: &str = "sha-224=:CAj2TmDViXn8tnbJbsk4Jw3qQkRa7vzTpOb42w==:,sha-256=:LCa0a2j/xo/5m0U8HTBBNBNCLXBkg7+g+YpeiGJm564=:,sha-384=:mMEf/f3VQGdrGhN8saIrKnA1DJpEFx1rEYDGvly7LuP3nVMsih3Z7y6OCOdSo7q7:,sha-512=:9/u6bgY2+JDlb7vzKD5STG+jIErimDgtYkdB0NxmODJuKCxBvl5CVNiCB3LFUYosWowMf37aGVlKfrU5RT4e1w==:";
198 assert_eq!(STR.parse::<ContentDigest>().unwrap().to_string(), STR);
199 }
200}