use std::sync::Arc;
use bytes::Bytes;
use reqwest::Method;
use serde::{Deserialize, Serialize};
use crate::client::{Inner, MAX_JSON_RESPONSE_BYTES, request};
use crate::swarm::{BatchId, Error, Reference};
#[derive(Clone, Debug)]
pub struct ApiService {
pub(crate) inner: Arc<Inner>,
}
impl ApiService {
pub(crate) fn new(inner: Arc<Inner>) -> Self {
Self { inner }
}
pub async fn pin(&self, reference: &Reference) -> Result<(), Error> {
let path = format!("pins/{}", reference.to_hex());
let builder = request(&self.inner, Method::POST, &path)?;
self.inner.send(builder).await?;
Ok(())
}
pub async fn unpin(&self, reference: &Reference) -> Result<(), Error> {
let path = format!("pins/{}", reference.to_hex());
let builder = request(&self.inner, Method::DELETE, &path)?;
self.inner.send(builder).await?;
Ok(())
}
pub async fn get_pin(&self, reference: &Reference) -> Result<bool, Error> {
let path = format!("pins/{}", reference.to_hex());
let builder = request(&self.inner, Method::GET, &path)?;
match self.inner.send(builder).await {
Ok(_) => Ok(true),
Err(e) if e.status() == Some(404) => Ok(false),
Err(e) => Err(e),
}
}
pub async fn list_pins(&self) -> Result<Vec<Reference>, Error> {
let builder = request(&self.inner, Method::GET, "pins")?;
#[derive(Deserialize)]
struct Resp {
references: Vec<Reference>,
}
let r: Resp = self.inner.send_json(builder).await?;
Ok(r.references)
}
pub async fn check_pins(
&self,
reference: Option<&Reference>,
) -> Result<Vec<PinIntegrity>, Error> {
let mut builder = request(&self.inner, Method::GET, "pins/check")?;
if let Some(r) = reference {
builder = builder.query(&[("ref", r.to_hex())]);
}
let resp = self.inner.send(builder).await?;
let bytes = Inner::read_capped(resp, MAX_JSON_RESPONSE_BYTES).await?;
let mut out = Vec::new();
for line in bytes.split(|&b| b == b'\n') {
let trimmed = trim_ws(line);
if trimmed.is_empty() {
continue;
}
let entry: PinIntegrity = serde_json::from_slice(trimmed)?;
out.push(entry);
}
Ok(out)
}
pub async fn create_tag(&self) -> Result<Tag, Error> {
let builder = request(&self.inner, Method::POST, "tags")?;
self.inner.send_json(builder).await
}
pub async fn get_tag(&self, uid: u32) -> Result<Tag, Error> {
let path = format!("tags/{uid}");
let builder = request(&self.inner, Method::GET, &path)?;
self.inner.send_json(builder).await
}
pub async fn retrieve_tag(&self, uid: u32) -> Result<Tag, Error> {
self.get_tag(uid).await
}
pub async fn list_tags(
&self,
offset: Option<u32>,
limit: Option<u32>,
) -> Result<Vec<Tag>, Error> {
let mut builder = request(&self.inner, Method::GET, "tags")?;
if let Some(o) = offset {
if o > 0 {
builder = builder.query(&[("offset", o.to_string())]);
}
}
if let Some(l) = limit {
if l > 0 {
builder = builder.query(&[("limit", l.to_string())]);
}
}
#[derive(Deserialize)]
struct Resp {
tags: Vec<Tag>,
}
let r: Resp = self.inner.send_json(builder).await?;
Ok(r.tags)
}
pub async fn delete_tag(&self, uid: u32) -> Result<(), Error> {
let path = format!("tags/{uid}");
let builder = request(&self.inner, Method::DELETE, &path)?;
self.inner.send(builder).await?;
Ok(())
}
pub async fn update_tag(&self, uid: u32, tag: &Tag) -> Result<(), Error> {
let path = format!("tags/{uid}");
let body = serde_json::to_vec(tag)?;
let builder = request(&self.inner, Method::PATCH, &path)?
.header("Content-Type", "application/json")
.body(Bytes::from(body));
self.inner.send(builder).await?;
Ok(())
}
pub async fn reupload(&self, reference: &Reference, batch_id: &BatchId) -> Result<(), Error> {
let path = format!("stewardship/{}", reference.to_hex());
let builder = request(&self.inner, Method::PUT, &path)?
.header("swarm-postage-batch-id", batch_id.to_hex());
self.inner.send(builder).await?;
Ok(())
}
pub async fn is_retrievable(&self, reference: &Reference) -> Result<bool, Error> {
let path = format!("stewardship/{}", reference.to_hex());
let builder = request(&self.inner, Method::GET, &path)?;
#[derive(Deserialize)]
struct Resp {
is_retrievable: bool,
}
#[derive(Deserialize)]
struct CamelResp {
#[serde(rename = "isRetrievable")]
is_retrievable: bool,
}
let resp = self.inner.send(builder).await?;
let bytes = Inner::read_capped(resp, MAX_JSON_RESPONSE_BYTES).await?;
if let Ok(r) = serde_json::from_slice::<CamelResp>(&bytes) {
return Ok(r.is_retrievable);
}
let r: Resp = serde_json::from_slice(&bytes)?;
Ok(r.is_retrievable)
}
pub async fn get_grantees(&self, reference: &Reference) -> Result<Vec<String>, Error> {
let path = format!("grantee/{}", reference.to_hex());
let builder = request(&self.inner, Method::GET, &path)?;
let v: Vec<String> = self.inner.send_json(builder).await?;
Ok(v)
}
pub async fn create_grantees(
&self,
batch_id: &BatchId,
grantees: &[String],
) -> Result<GranteeResponse, Error> {
#[derive(Serialize)]
struct Body<'a> {
grantees: &'a [String],
}
let body = serde_json::to_vec(&Body { grantees })?;
let builder = request(&self.inner, Method::POST, "grantee")?
.header("Content-Type", "application/json")
.header("Swarm-Postage-Batch-Id", batch_id.to_hex())
.body(Bytes::from(body));
self.inner.send_json(builder).await
}
pub async fn patch_grantees(
&self,
batch_id: &BatchId,
reference: &Reference,
history_address: &Reference,
add: &[String],
revoke: &[String],
) -> Result<GranteeResponse, Error> {
#[derive(Serialize)]
struct Body<'a> {
#[serde(skip_serializing_if = "<[String]>::is_empty")]
add: &'a [String],
#[serde(skip_serializing_if = "<[String]>::is_empty")]
revoke: &'a [String],
}
let body = serde_json::to_vec(&Body { add, revoke })?;
let path = format!("grantee/{}", reference.to_hex());
let builder = request(&self.inner, Method::PATCH, &path)?
.header("Content-Type", "application/json")
.header("Swarm-Postage-Batch-Id", batch_id.to_hex())
.header("Swarm-Act-History-Address", history_address.to_hex())
.body(Bytes::from(body));
self.inner.send_json(builder).await
}
pub async fn post_envelope(
&self,
batch_id: &BatchId,
reference: &Reference,
) -> Result<EnvelopeResponse, Error> {
let path = format!("envelope/{}", reference.to_hex());
let builder = request(&self.inner, Method::POST, &path)?
.header("Swarm-Postage-Batch-Id", batch_id.to_hex());
self.inner.send_json(builder).await
}
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
pub struct PinIntegrity {
pub reference: Reference,
pub total: u64,
pub missing: u64,
pub invalid: u64,
}
impl PinIntegrity {
pub fn is_healthy(&self) -> bool {
self.missing == 0 && self.invalid == 0
}
}
fn trim_ws(mut s: &[u8]) -> &[u8] {
while let [first, rest @ ..] = s {
if first.is_ascii_whitespace() {
s = rest;
} else {
break;
}
}
while let [rest @ .., last] = s {
if last.is_ascii_whitespace() {
s = rest;
} else {
break;
}
}
s
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
pub struct GranteeResponse {
#[serde(rename = "ref")]
pub reference: String,
#[serde(rename = "historyref")]
pub history_reference: String,
}
#[derive(Clone, Debug, PartialEq, Eq, Deserialize)]
pub struct EnvelopeResponse {
pub issuer: String,
pub index: String,
pub timestamp: String,
pub signature: String,
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct Tag {
#[serde(default)]
pub uid: u32,
#[serde(default)]
pub name: String,
#[serde(default)]
pub total: i64,
#[serde(default)]
pub split: i64,
#[serde(default)]
pub seen: i64,
#[serde(default)]
pub stored: i64,
#[serde(default)]
pub sent: i64,
#[serde(default)]
pub synced: i64,
#[serde(default)]
pub address: String,
#[serde(default, rename = "startedAt")]
pub started_at: String,
}