1use super::{Client, Result};
5
6use std::io::{copy, Read, Write};
7use std::str::FromStr;
8
9use drawbridge_type::digest::{Algorithms, ContentDigest};
10use drawbridge_type::Meta;
11
12use anyhow::{anyhow, bail, Context};
13use http::header::{CONTENT_LENGTH, CONTENT_TYPE};
14use http::StatusCode;
15use mime::Mime;
16use ureq::serde::{Deserialize, Serialize};
17use ureq::{Request, Response};
18
19fn parse_header<T>(req: &Response, name: &str) -> Result<T>
20where
21 T: FromStr,
22 T::Err: 'static + Sync + Send + std::error::Error,
23{
24 req.header(name)
25 .ok_or_else(|| anyhow!("missing `{name}` header"))?
26 .parse()
27 .context(format!("failed to parse `{name}` header"))
28}
29
30#[derive(Clone, Debug)]
31pub struct Entity<'a> {
32 client: &'a Client,
33 path: String,
34}
35
36fn parse_ureq_error(e: ureq::Error) -> anyhow::Error {
37 match e {
38 ureq::Error::Status(code, msg) => match msg.into_string() {
39 Ok(msg) if !msg.is_empty() => {
40 anyhow!(msg).context(format!("request failed with status code `{code}`"))
41 }
42 _ => anyhow!("request failed with status code `{code}`"),
43 },
44
45 ureq::Error::Transport(e) => anyhow::Error::new(e).context("transport layer failure"),
46 }
47}
48
49impl<'a> Entity<'a> {
50 pub fn new(client: &'a Client) -> Self {
51 Self {
52 client,
53 path: Default::default(),
54 }
55 }
56
57 pub fn child(&self, path: &str) -> Self {
59 Self {
60 client: self.client,
61 path: format!("{}/{}", self.path, path),
62 }
63 }
64
65 pub(super) fn create_request(&self, hash: &ContentDigest, mime: &Mime) -> Result<Request> {
66 let token = self.client.token.as_ref().ok_or_else(|| {
67 anyhow!("endpoint requires authorization, but no token was configured")
68 })?;
69 let url = self.client.url(&self.path)?;
70 Ok(self
71 .client
72 .inner
73 .put(url.as_str())
74 .set("Authorization", &format!("Bearer {token}"))
75 .set("Content-Digest", &hash.to_string())
76 .set(CONTENT_TYPE.as_str(), mime.as_ref()))
77 }
78
79 pub(super) fn create_bytes(&self, mime: &Mime, data: impl AsRef<[u8]>) -> Result<bool> {
80 let data = data.as_ref();
81 let (n, hash) = Algorithms::default()
82 .read_sync(data)
83 .context("failed to compute content digest")?;
84 if n != data.len() as u64 {
85 bail!(
86 "invalid amount of bytes read, expected: {}, got {n}",
87 data.len(),
88 )
89 }
90 let res = self
91 .create_request(&hash, mime)?
92 .send_bytes(data)
93 .map_err(parse_ureq_error)?;
94 match StatusCode::from_u16(res.status()) {
95 Ok(StatusCode::CREATED) => Ok(true),
96 Ok(StatusCode::OK) => Ok(false),
97 _ => bail!("unexpected status code: {}", res.status()),
98 }
99 }
100
101 pub(super) fn create_json(&self, mime: &Mime, val: &impl Serialize) -> Result<bool> {
102 let buf = serde_json::to_vec(val).context("failed to encode value to JSON")?;
103 self.create_bytes(mime, buf)
104 }
105
106 pub(super) fn create_from(
107 &self,
108 Meta { hash, size, mime }: &Meta,
109 rdr: impl Read,
110 ) -> Result<bool> {
111 let res = self
112 .create_request(hash, mime)?
113 .set(CONTENT_LENGTH.as_str(), &size.to_string())
114 .send(rdr)
115 .map_err(parse_ureq_error)?;
116 match StatusCode::from_u16(res.status()) {
117 Ok(StatusCode::CREATED) => Ok(true),
118 Ok(StatusCode::OK) => Ok(false),
119 _ => bail!("unexpected status code: {}", res.status()),
120 }
121 }
122
123 pub fn get(&self) -> Result<(u64, Mime, impl Read)> {
124 let url = self.client.url(&self.path)?;
125 let mut req = self.client.inner.get(url.as_str());
126 if let Some(ref token) = self.client.token {
127 req = req.set("Authorization", &format!("Bearer {token}"))
128 }
129 let res = req
130 .call()
131 .map_err(parse_ureq_error)
132 .context("GET request failed")?;
133
134 let hash: ContentDigest = parse_header(&res, "Content-Digest")?;
135 let mime = parse_header(&res, CONTENT_TYPE.as_str())?;
136 let size = parse_header(&res, CONTENT_LENGTH.as_str())?;
137 match StatusCode::from_u16(res.status()) {
138 Ok(StatusCode::OK) => Ok((size, mime, hash.verifier(res.into_reader().take(size)))),
139 _ => bail!("unexpected status code: {}", res.status()),
140 }
141 }
142
143 pub fn get_to(&self, dst: &mut impl Write) -> Result<(u64, Mime)> {
144 let (size, mime, mut rdr) = self.get()?;
145 let n = copy(&mut rdr, dst)?;
146 if n != size {
147 bail!(
148 "invalid amount of bytes read, expected {}, read {}",
149 size,
150 n,
151 )
152 }
153 Ok((size, mime))
154 }
155
156 pub fn get_json<T>(&self) -> Result<T>
157 where
158 for<'de> T: Deserialize<'de>,
159 {
160 let (_, _, rdr) = self.get()?;
161 serde_json::from_reader(rdr).context("failed to decode JSON")
162 }
163
164 pub fn get_bytes(&self) -> Result<(Mime, Vec<u8>)> {
165 let (size, mime, mut rdr) = self.get()?;
166 let mut buf =
167 Vec::with_capacity(size.try_into().context("failed to convert u64 to usize")?);
168 let n = copy(&mut rdr, &mut buf).context("I/O failure")?;
169 if n != size {
170 bail!(
171 "invalid amount of bytes read, expected {}, read {}",
172 size,
173 n,
174 )
175 };
176 Ok((mime, buf))
177 }
178
179 pub fn get_string(&self) -> Result<(Mime, String)> {
180 let (size, mime, mut rdr) = self.get()?;
181 let size = size.try_into().context("failed to convert u64 to usize")?;
182 let mut s = String::with_capacity(size);
183 let n = rdr.read_to_string(&mut s).context("I/O failure")?;
184 if n != size {
185 bail!(
186 "invalid amount of bytes read, expected {}, read {}",
187 size,
188 n,
189 )
190 };
191 Ok((mime, s))
192 }
193}