1use std::sync::Arc;
5
6use bytes::Bytes;
7use reqwest::Method;
8use serde::{Deserialize, Serialize};
9
10use crate::client::{Inner, request};
11use crate::swarm::{BatchId, Error, Reference};
12
13#[derive(Clone, Debug)]
16pub struct ApiService {
17 pub(crate) inner: Arc<Inner>,
18}
19
20impl ApiService {
21 pub(crate) fn new(inner: Arc<Inner>) -> Self {
22 Self { inner }
23 }
24
25 pub async fn pin(&self, reference: &Reference) -> Result<(), Error> {
29 let path = format!("pins/{}", reference.to_hex());
30 let builder = request(&self.inner, Method::POST, &path)?;
31 self.inner.send(builder).await?;
32 Ok(())
33 }
34
35 pub async fn unpin(&self, reference: &Reference) -> Result<(), Error> {
37 let path = format!("pins/{}", reference.to_hex());
38 let builder = request(&self.inner, Method::DELETE, &path)?;
39 self.inner.send(builder).await?;
40 Ok(())
41 }
42
43 pub async fn get_pin(&self, reference: &Reference) -> Result<bool, Error> {
47 let path = format!("pins/{}", reference.to_hex());
48 let builder = request(&self.inner, Method::GET, &path)?;
49 match self.inner.send(builder).await {
50 Ok(_) => Ok(true),
51 Err(e) if e.status() == Some(404) => Ok(false),
52 Err(e) => Err(e),
53 }
54 }
55
56 pub async fn list_pins(&self) -> Result<Vec<Reference>, Error> {
58 let builder = request(&self.inner, Method::GET, "pins")?;
59 #[derive(Deserialize)]
60 struct Resp {
61 references: Vec<Reference>,
62 }
63 let r: Resp = self.inner.send_json(builder).await?;
64 Ok(r.references)
65 }
66
67 pub async fn check_pins(
80 &self,
81 reference: Option<&Reference>,
82 ) -> Result<Vec<PinIntegrity>, Error> {
83 let mut builder = request(&self.inner, Method::GET, "pins/check")?;
84 if let Some(r) = reference {
85 builder = builder.query(&[("ref", r.to_hex())]);
86 }
87 let resp = self.inner.send(builder).await?;
88 let bytes = resp.bytes().await?;
89 let mut out = Vec::new();
90 for line in bytes.split(|&b| b == b'\n') {
91 let trimmed = trim_ws(line);
92 if trimmed.is_empty() {
93 continue;
94 }
95 let entry: PinIntegrity = serde_json::from_slice(trimmed)?;
96 out.push(entry);
97 }
98 Ok(out)
99 }
100
101 pub async fn create_tag(&self) -> Result<Tag, Error> {
105 let builder = request(&self.inner, Method::POST, "tags")?;
106 self.inner.send_json(builder).await
107 }
108
109 pub async fn get_tag(&self, uid: u32) -> Result<Tag, Error> {
111 let path = format!("tags/{uid}");
112 let builder = request(&self.inner, Method::GET, &path)?;
113 self.inner.send_json(builder).await
114 }
115
116 pub async fn retrieve_tag(&self, uid: u32) -> Result<Tag, Error> {
118 self.get_tag(uid).await
119 }
120
121 pub async fn list_tags(
123 &self,
124 offset: Option<u32>,
125 limit: Option<u32>,
126 ) -> Result<Vec<Tag>, Error> {
127 let mut builder = request(&self.inner, Method::GET, "tags")?;
128 if let Some(o) = offset {
129 if o > 0 {
130 builder = builder.query(&[("offset", o.to_string())]);
131 }
132 }
133 if let Some(l) = limit {
134 if l > 0 {
135 builder = builder.query(&[("limit", l.to_string())]);
136 }
137 }
138 #[derive(Deserialize)]
139 struct Resp {
140 tags: Vec<Tag>,
141 }
142 let r: Resp = self.inner.send_json(builder).await?;
143 Ok(r.tags)
144 }
145
146 pub async fn delete_tag(&self, uid: u32) -> Result<(), Error> {
148 let path = format!("tags/{uid}");
149 let builder = request(&self.inner, Method::DELETE, &path)?;
150 self.inner.send(builder).await?;
151 Ok(())
152 }
153
154 pub async fn update_tag(&self, uid: u32, tag: &Tag) -> Result<(), Error> {
156 let path = format!("tags/{uid}");
157 let body = serde_json::to_vec(tag)?;
158 let builder = request(&self.inner, Method::PATCH, &path)?
159 .header("Content-Type", "application/json")
160 .body(Bytes::from(body));
161 self.inner.send(builder).await?;
162 Ok(())
163 }
164
165 pub async fn reupload(&self, reference: &Reference, batch_id: &BatchId) -> Result<(), Error> {
169 let path = format!("stewardship/{}", reference.to_hex());
170 let builder = request(&self.inner, Method::PUT, &path)?
171 .header("swarm-postage-batch-id", batch_id.to_hex());
172 self.inner.send(builder).await?;
173 Ok(())
174 }
175
176 pub async fn is_retrievable(&self, reference: &Reference) -> Result<bool, Error> {
180 let path = format!("stewardship/{}", reference.to_hex());
181 let builder = request(&self.inner, Method::GET, &path)?;
182 #[derive(Deserialize)]
183 struct Resp {
184 is_retrievable: bool,
185 }
186 #[derive(Deserialize)]
187 struct CamelResp {
188 #[serde(rename = "isRetrievable")]
189 is_retrievable: bool,
190 }
191 let resp = self.inner.send(builder).await?;
194 let bytes = resp.bytes().await?;
195 if let Ok(r) = serde_json::from_slice::<CamelResp>(&bytes) {
196 return Ok(r.is_retrievable);
197 }
198 let r: Resp = serde_json::from_slice(&bytes)?;
199 Ok(r.is_retrievable)
200 }
201
202 pub async fn get_grantees(&self, reference: &Reference) -> Result<Vec<String>, Error> {
206 let path = format!("grantee/{}", reference.to_hex());
207 let builder = request(&self.inner, Method::GET, &path)?;
208 let v: Vec<String> = self.inner.send_json(builder).await?;
211 Ok(v)
212 }
213
214 pub async fn create_grantees(
216 &self,
217 batch_id: &BatchId,
218 grantees: &[String],
219 ) -> Result<GranteeResponse, Error> {
220 #[derive(Serialize)]
221 struct Body<'a> {
222 grantees: &'a [String],
223 }
224 let body = serde_json::to_vec(&Body { grantees })?;
225 let builder = request(&self.inner, Method::POST, "grantee")?
226 .header("Content-Type", "application/json")
227 .header("Swarm-Postage-Batch-Id", batch_id.to_hex())
228 .body(Bytes::from(body));
229 self.inner.send_json(builder).await
230 }
231
232 pub async fn patch_grantees(
234 &self,
235 batch_id: &BatchId,
236 reference: &Reference,
237 history_address: &Reference,
238 add: &[String],
239 revoke: &[String],
240 ) -> Result<GranteeResponse, Error> {
241 #[derive(Serialize)]
242 struct Body<'a> {
243 #[serde(skip_serializing_if = "<[String]>::is_empty")]
244 add: &'a [String],
245 #[serde(skip_serializing_if = "<[String]>::is_empty")]
246 revoke: &'a [String],
247 }
248 let body = serde_json::to_vec(&Body { add, revoke })?;
249 let path = format!("grantee/{}", reference.to_hex());
250 let builder = request(&self.inner, Method::PATCH, &path)?
251 .header("Content-Type", "application/json")
252 .header("Swarm-Postage-Batch-Id", batch_id.to_hex())
253 .header("Swarm-Act-History-Address", history_address.to_hex())
254 .body(Bytes::from(body));
255 self.inner.send_json(builder).await
256 }
257
258 pub async fn post_envelope(
264 &self,
265 batch_id: &BatchId,
266 reference: &Reference,
267 ) -> Result<EnvelopeResponse, Error> {
268 let path = format!("envelope/{}", reference.to_hex());
269 let builder = request(&self.inner, Method::POST, &path)?
270 .header("Swarm-Postage-Batch-Id", batch_id.to_hex());
271 self.inner.send_json(builder).await
272 }
273}
274
275#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
278pub struct PinIntegrity {
279 pub reference: Reference,
281 pub total: u64,
283 pub missing: u64,
285 pub invalid: u64,
287}
288
289impl PinIntegrity {
290 pub fn is_healthy(&self) -> bool {
293 self.missing == 0 && self.invalid == 0
294 }
295}
296
297fn trim_ws(mut s: &[u8]) -> &[u8] {
298 while let [first, rest @ ..] = s {
299 if first.is_ascii_whitespace() {
300 s = rest;
301 } else {
302 break;
303 }
304 }
305 while let [rest @ .., last] = s {
306 if last.is_ascii_whitespace() {
307 s = rest;
308 } else {
309 break;
310 }
311 }
312 s
313}
314
315#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
319pub struct GranteeResponse {
320 #[serde(rename = "ref")]
322 pub reference: String,
323 #[serde(rename = "historyref")]
325 pub history_reference: String,
326}
327
328#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
330pub struct EnvelopeResponse {
331 pub issuer: String,
333 pub index: String,
335 pub timestamp: String,
337 pub signature: String,
339}
340
341#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
343pub struct Tag {
344 #[serde(default)]
346 pub uid: u32,
347 #[serde(default)]
349 pub name: String,
350 #[serde(default)]
352 pub total: i64,
353 #[serde(default)]
355 pub split: i64,
356 #[serde(default)]
358 pub seen: i64,
359 #[serde(default)]
361 pub stored: i64,
362 #[serde(default)]
364 pub sent: i64,
365 #[serde(default)]
367 pub synced: i64,
368 #[serde(default)]
370 pub address: String,
371 #[serde(default, rename = "startedAt")]
373 pub started_at: String,
374}