bee/file/data.rs
1//! `/bytes` endpoints: upload / download / probe.
2
3use bytes::Bytes;
4use reqwest::Method;
5use serde::Deserialize;
6
7use crate::api::{
8 DownloadOptions, RedundantUploadOptions, UploadResult, prepare_download_headers,
9 prepare_redundant_upload_headers,
10};
11use crate::client::{Inner, request};
12use crate::swarm::{BatchId, Error, Reference};
13
14use super::FileApi;
15
16/// Result of [`FileApi::probe_data`]: the size of the data behind a
17/// `/bytes` reference, learned via `HEAD` without downloading the body.
18#[derive(Clone, Debug, PartialEq, Eq)]
19pub struct ReferenceInformation {
20 /// `Content-Length` of the referenced data, in bytes.
21 pub content_length: u64,
22}
23
24#[derive(Deserialize)]
25struct UploadBody {
26 reference: String,
27}
28
29impl FileApi {
30 /// Upload raw bytes via `POST /bytes`. The body is sent as
31 /// `application/octet-stream`. Returns an [`UploadResult`] with the
32 /// content reference, optional tag UID, and (when ACT was
33 /// requested) the history address.
34 ///
35 /// # Examples
36 ///
37 /// ```no_run
38 /// use bee::Client;
39 /// use bee::swarm::BatchId;
40 /// use bytes::Bytes;
41 ///
42 /// # async fn run(batch_id: BatchId) -> Result<(), bee::Error> {
43 /// let client = Client::new("http://localhost:1633")?;
44 /// let result = client
45 /// .file()
46 /// .upload_data(&batch_id, Bytes::from_static(b"Hello Swarm!"), None)
47 /// .await?;
48 /// println!("reference: {}", result.reference.to_hex());
49 /// # Ok(()) }
50 /// ```
51 pub async fn upload_data(
52 &self,
53 batch_id: &BatchId,
54 data: impl Into<Bytes>,
55 opts: Option<&RedundantUploadOptions>,
56 ) -> Result<UploadResult, Error> {
57 let builder = request(&self.inner, Method::POST, "bytes")?
58 .header("Content-Type", "application/octet-stream")
59 .body(data.into());
60 let builder =
61 Inner::apply_headers(builder, prepare_redundant_upload_headers(batch_id, opts));
62 let resp = self.inner.send(builder).await?;
63 let headers = resp.headers().clone();
64 let body: UploadBody = serde_json::from_slice(&resp.bytes().await?)?;
65 UploadResult::from_response(&body.reference, &headers)
66 }
67
68 /// Download raw bytes via `GET /bytes/{ref}`. Returns the full body
69 /// in memory. For streaming downloads use
70 /// [`FileApi::download_data_response`](Self::download_data_response).
71 ///
72 /// # Examples
73 ///
74 /// ```no_run
75 /// use bee::Client;
76 /// use bee::swarm::Reference;
77 ///
78 /// # async fn run(reference: Reference) -> Result<(), bee::Error> {
79 /// let client = Client::new("http://localhost:1633")?;
80 /// let body = client.file().download_data(&reference, None).await?;
81 /// println!("downloaded {} bytes", body.len());
82 /// # Ok(()) }
83 /// ```
84 pub async fn download_data(
85 &self,
86 reference: &Reference,
87 opts: Option<&DownloadOptions>,
88 ) -> Result<Bytes, Error> {
89 let resp = self.download_data_response(reference, opts).await?;
90 Ok(resp.bytes().await?)
91 }
92
93 /// Download raw bytes via `GET /bytes/{ref}` and return the raw
94 /// [`reqwest::Response`] for streaming. The caller drives reading
95 /// from `resp.bytes_stream()` or `resp.chunk()`.
96 pub async fn download_data_response(
97 &self,
98 reference: &Reference,
99 opts: Option<&DownloadOptions>,
100 ) -> Result<reqwest::Response, Error> {
101 let path = format!("bytes/{}", reference.to_hex());
102 let builder = request(&self.inner, Method::GET, &path)?;
103 let builder = Inner::apply_headers(builder, prepare_download_headers(opts));
104 self.inner.send(builder).await
105 }
106
107 /// Probe the size of the data behind a `/bytes` reference using
108 /// a `HEAD` request. Mirrors bee-js `Bee.probeData`.
109 pub async fn probe_data(&self, reference: &Reference) -> Result<ReferenceInformation, Error> {
110 let path = format!("bytes/{}", reference.to_hex());
111 let builder = request(&self.inner, Method::HEAD, &path)?;
112 let resp = self.inner.send(builder).await?;
113 let content_length = resp
114 .headers()
115 .get(reqwest::header::CONTENT_LENGTH)
116 .and_then(|v| v.to_str().ok())
117 .and_then(|s| s.parse::<u64>().ok())
118 .or_else(|| resp.content_length())
119 .ok_or_else(|| Error::argument("missing Content-Length"))?;
120 Ok(ReferenceInformation { content_length })
121 }
122}