Skip to main content

boxxkite_client/
volumes.rs

1//! Independent, PVC-backed storage volumes: `POST/GET/DELETE /v1/volumes*`.
2//! Mirrors `sdk-python`'s `create_volume`/`get_volume`/`list_volumes`/
3//! `delete_volume`.
4
5use reqwest::Method;
6use serde::{Deserialize, Serialize};
7
8use crate::client::Client;
9use crate::error::BoxxkiteError;
10
11/// Builder for `POST /v1/volumes`'s optional fields.
12#[derive(Debug, Clone, Default, Serialize)]
13pub struct CreateVolumeOptions {
14    #[serde(skip_serializing_if = "Option::is_none")]
15    label: Option<String>,
16}
17
18impl CreateVolumeOptions {
19    pub fn new() -> Self {
20        Self::default()
21    }
22
23    pub fn label(mut self, label: impl Into<String>) -> Self {
24        self.label = Some(label.into());
25        self
26    }
27}
28
29/// An independent, PVC-backed storage volume (`VolumeOut`/`VolumeAccepted`).
30/// [`Client::create_volume`]'s response only ever populates `id`/`label`/
31/// `status`/`created_at` -- poll [`Client::get_volume`] for `status` to
32/// reach `"ready"` (or `"failed"`) before mounting it via
33/// [`crate::CreateSandboxOptions::volume_mounts`].
34#[derive(Debug, Clone, Deserialize)]
35pub struct Volume {
36    pub id: String,
37    pub label: Option<String>,
38    #[serde(default)]
39    pub size_gb: f64,
40    /// `"queued"`, `"creating"`, `"ready"`, `"failed"`, or `"deleting"`.
41    pub status: String,
42    pub pvc_name: Option<String>,
43    pub failure_reason: Option<String>,
44    pub created_at: String,
45}
46
47impl Client {
48    /// `POST /v1/volumes` -- create an independent storage volume. Returns
49    /// immediately with a pending record; poll [`Client::get_volume`] for
50    /// `status` before mounting it into a sandbox.
51    ///
52    /// `size_gb` is required (max 1024).
53    pub async fn create_volume(
54        &self,
55        size_gb: f64,
56        options: CreateVolumeOptions,
57    ) -> Result<Volume, BoxxkiteError> {
58        #[derive(Serialize)]
59        struct Body<'a> {
60            size_gb: f64,
61            #[serde(skip_serializing_if = "Option::is_none")]
62            label: Option<&'a str>,
63        }
64        let body = Body {
65            size_gb,
66            label: options.label.as_deref(),
67        };
68        let builder = self.request(Method::POST, "/v1/volumes").json(&body);
69        self.send(builder).await
70    }
71
72    /// `GET /v1/volumes/{id}` -- a storage volume's status and details.
73    pub async fn get_volume(&self, volume_id: &str) -> Result<Volume, BoxxkiteError> {
74        let builder = self.request(Method::GET, &format!("/v1/volumes/{volume_id}"));
75        self.send(builder).await
76    }
77
78    /// `GET /v1/volumes` -- every storage volume created for this account.
79    pub async fn list_volumes(&self) -> Result<Vec<Volume>, BoxxkiteError> {
80        let builder = self.request(Method::GET, "/v1/volumes");
81        self.send_or_default(builder).await
82    }
83
84    /// `DELETE /v1/volumes/{id}` -- delete a storage volume.
85    pub async fn delete_volume(&self, volume_id: &str) -> Result<(), BoxxkiteError> {
86        let builder = self.request(Method::DELETE, &format!("/v1/volumes/{volume_id}"));
87        self.send_no_content(builder).await
88    }
89}