Response

The server sends exactly one response, whose payload is exactly the byte 0 when the volume no longer exists; exactly the byte 1, 2 or 3 when a running container, a FUSE serve or an operation holds the volume and it was not deleted; exactly the byte 4 when the server keeps no volume; or the byte 5 followed by an error as one JSON value; and the response finish follows it.

The server sends exactly one response on the scope. The response finish follows the response, and no frame follows the finish.

[0]                  deleted
[1]                  not deleted: a running container holds the volume
[2]                  not deleted: a FUSE serve holds the volume
[3]                  not deleted: another operation holds the volume
[4]                  not deleted: the server keeps no volume
[5][error JSON …]    not deleted
  • The sequence. Exactly one response precedes the response finish, and nothing follows the finish. A response finish that no response precedes states that the request was not served, as Endpoints provides; whether the volume exists is then not stated, and the client learns it from a volumes::list scope.
  • Deleted. A payload whose first byte is 0 states that the volume no longer exists. The server sends it after the volume is gone. From the moment the server sends it, every volumes::list scope the client opens omits the volume, and every list scope the client holds open that was sent the volume as added is sent it as removed: a listing the client reads as whole after receiving the byte 0 never contains it, and no interval in which the volume is deleted and still listed as available exists. The server sends nothing after the byte 0, and a client ignores every byte that follows it.
  • The refusals, and their order. The server decides the bytes 1, 2 and 3 in the order Capabilities states for an operation, the first rule that applies deciding. On a server that keeps no volume, the byte 4 is the only response, sent before anything is looked up. Every refusal states that the volume was not deleted: the volume, its content, and the client’s listing are as they were before the request. A client ignores every byte that follows the byte 1, 2, 3 or 4.
  • Mounted. A payload that is the byte 1 and nothing else states that a running container of the client holds the volume at the time of the request.
  • FUSE served. A payload that is the byte 2 and nothing else states that a volumes::fuse_serve of the client holds the volume at the time of the request.
  • Held. A payload that is the byte 3 and nothing else states that the volume is under a volumes::stat, a volumes::read, a volumes::write, a volumes::filetree, a volumes::edit or another volumes::delete at the time of the request.
  • Unsupported. A payload that is the byte 4 and nothing else states that the server holds out none of the three mode objects and keeps no volume, as Capabilities provides.
  • The error. A payload whose first byte is 5 carries an error after that byte, running to the end of the payload. The error is exactly one JSON value, in the form defined on the volumes::list response page. This revision prescribes nothing about its content. The error states that the volume exists as a result of the request, as it did before it, and that the reason is none of the refusals above.
  • Malformed. A payload with no byte, a payload whose first byte is none of 0 to 5, and a payload whose bytes after 5 are not one JSON value are malformed.

The response is defined by diverge-sdk-rs/src/provider/endpoints/volumes/delete/server/response/frame.rs:

//! What a server's response frame carries for a volume deletion.

use std::fmt;

use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};
use crate::shared::error::Error;

/// The volume is gone, or it is mounted and stays, or it is not gone
/// for some other reason.
///
/// One of these on channel `0`, then the scope finishes. A payload
/// leads with one byte saying which — `0` for
/// [`Deleted`](Self::Deleted), `1` for [`Mounted`](Self::Mounted), `2`
/// for [`FuseServed`](Self::FuseServed), `3` for [`Held`](Self::Held),
/// `4` for [`Unsupported`](Self::Unsupported), `5` for
/// [`Error`](Self::Error) — and for all but the last there is nothing
/// after it, because saying so IS the whole message.
///
/// | the scope ends with | means |
/// |----------------------|-------|
/// | [`Deleted`](Self::Deleted), then a finish | the volume is gone and a listing will not show it |
/// | [`Mounted`](Self::Mounted), then a finish | it is mounted in a running container, and nothing was changed |
/// | [`FuseServed`](Self::FuseServed), then a finish | a FUSE serve holds it, and nothing was changed |
/// | [`Held`](Self::Held), then a finish | another operation has it to itself, and nothing was changed |
/// | an [`Error`](Self::Error), then a finish | it is not gone, for some other reason, and a caller should assume it is intact |
///
/// # Mounted, FUSE served and held are answers, not errors
///
/// A provider MUST refuse to delete a volume that something holds at
/// the time of the request — a running container, a FUSE serve, or
/// another operation — and it says which with
/// [`Mounted`](Self::Mounted), [`FuseServed`](Self::FuseServed) or
/// [`Held`](Self::Held) rather than with an [`Error`](Self::Error) a
/// caller could not tell from any other failure. The distinction is
/// what a caller acts on: a container or a FUSE serve to stop, an
/// operation to wait for, and then ask again; a failure is neither. On the server half the handler answers it from
/// the volume's `lock`, which
/// a running container holds for its life; the manager is never
/// asked.
///
/// # Gone means gone, not emptied
///
/// The volume itself no longer exists. A caller that wanted the space
/// back with the name kept deletes and creates — which is two asks
/// because they are two things, and a provider that emptied one in
/// place would be doing something this frame has no way to distinguish
/// from the other.
///
/// # There is no partial deletion to report
///
/// A provider either finishes destroying the volume or reports an
/// error. What it leaves behind on failure is its own business and not
/// a state a caller can observe: a
/// [`list`](crate::provider::endpoints::volumes::list) shows the volume or it
/// does not, and that is the only answer this protocol offers about
/// whether something exists.
#[derive(Debug, Clone, PartialEq)]
pub enum Frame {
    /// The volume is gone. Tag `0`.
    Deleted,
    /// The volume is mounted in a running container, and was not
    /// deleted. Tag `1`.
    ///
    /// Nothing was changed: the volume, its content, and the listing
    /// are as they were.
    Mounted,
    /// A FUSE serve holds the volume, and it was not deleted.
    /// Tag `2`.
    ///
    /// Nothing was changed; a caller stops the FUSE serve and asks
    /// again.
    FuseServed,
    /// Another operation — a stat, a read, a write, a filetree, an
    /// edit or a delete — has the volume to itself, and it was not
    /// deleted. Tag `3`.
    ///
    /// Nothing was changed; a caller waits for it to end and asks
    /// again.
    Held,
    /// The provider keeps no volumes: no mode is among its capabilities,
    /// see [`info`](crate::provider::endpoints::info), and every volume
    /// endpoint answers this. Tag `4`.
    ///
    /// Carries nothing. Nothing was looked at; a caller asks another
    /// provider.
    Unsupported,
    /// A failure. Tag `5`.
    ///
    /// See [`shared::error::Error`](crate::shared::error::Error) for
    /// why it says so little.
    Error(Error),
}

/// Tag for [`Frame::Deleted`].
const DELETED: u8 = 0;

/// Tag for [`Frame::Mounted`].
const MOUNTED: u8 = 1;

/// Tag for [`Frame::FuseServed`].
const FUSE_SERVED: u8 = 2;

/// Tag for [`Frame::Held`].
const HELD: u8 = 3;

/// Tag for [`Frame::Unsupported`].
const UNSUPPORTED: u8 = 4;

/// Tag for [`Frame::Error`].
const ERROR: u8 = 5;

/// A tag, and for a failure the JSON after it. Postcard encodes the
/// rest of [`volumes`](crate::provider::endpoints::volumes) and encodes nothing
/// here — an [`Error`](Frame::Error) is a
/// [`serde_json::Value`], which deserializes through
/// `deserialize_any` and so cannot come back out of a format with no
/// self-description. The tag chooses the format, one variant at a
/// time.
impl Encode for Frame {
    /// The ordinary JSON failure, from the one variant that has one.
    /// A lone tag byte cannot fail.
    type Error = serde_json::Error;

    // Spelled out rather than `Self::Error`: this enum has a variant
    // called `Error`, so the associated type is ambiguous by that name.
    fn encode(
        &self,
        out: &mut Writer<'_>,
    ) -> Result<(), serde_json::Error> {
        match self {
            Frame::Deleted => {
                out.extend_from_slice(&[DELETED]);
                Ok(())
            }
            Frame::Mounted => {
                out.extend_from_slice(&[MOUNTED]);
                Ok(())
            }
            Frame::FuseServed => {
                out.extend_from_slice(&[FUSE_SERVED]);
                Ok(())
            }
            Frame::Held => {
                out.extend_from_slice(&[HELD]);
                Ok(())
            }
            Frame::Unsupported => {
                out.extend_from_slice(&[UNSUPPORTED]);
                Ok(())
            }
            Frame::Error(error) => {
                out.extend_from_slice(&[ERROR]);
                error.encode(out)
            }
        }
    }
}

impl Decode<'_> for Frame {
    /// Three ways to fail, and only one of them is a parse.
    type Error = FrameError;

    // Spelled out for the same reason as `encode` above.
    fn decode(bytes: &[u8]) -> Result<Self, FrameError> {
        let (tag, rest) = bytes.split_first().ok_or(FrameError::Empty)?;
        match *tag {
            DELETED => Ok(Frame::Deleted),
            MOUNTED => Ok(Frame::Mounted),
            FUSE_SERVED => Ok(Frame::FuseServed),
            HELD => Ok(Frame::Held),
            UNSUPPORTED => Ok(Frame::Unsupported),
            ERROR => {
                Error::decode(rest).map(Frame::Error).map_err(FrameError::Error)
            }
            tag => Err(FrameError::UnknownTag(tag)),
        }
    }
}

/// A volume deletion result that could not be read.
#[derive(Debug)]
pub enum FrameError {
    /// No bytes at all, so not even a tag.
    Empty,
    /// A tag that is none of this frame's six.
    UnknownTag(u8),
    /// The error did not parse.
    Error(serde_json::Error),
}

impl fmt::Display for FrameError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            FrameError::Empty => {
                f.write_str("volume deletion result frame is empty")
            }
            FrameError::UnknownTag(tag) => {
                write!(f, "unknown volume deletion result frame tag {tag}")
            }
            FrameError::Error(error) => {
                write!(f, "volume deletion error did not parse: {error}")
            }
        }
    }
}

impl std::error::Error for FrameError {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            FrameError::Error(error) => Some(error),
            FrameError::Empty | FrameError::UnknownTag(_) => None,
        }
    }
}