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
0states that the volume no longer exists. The server sends it after the volume is gone. From the moment the server sends it, everyvolumes::listscope 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 byte0never contains it, and no interval in which the volume is deleted and still listed as available exists. The server sends nothing after the byte0, and a client ignores every byte that follows it. - The refusals, and their order. The server decides the bytes
1,2and3in the order Capabilities states for an operation, the first rule that applies deciding. On a server that keeps no volume, the byte4is 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 byte1,2,3or4. - Mounted. A payload that is the byte
1and 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
2and 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
3and nothing else states that the volume is under a volumes::stat, a volumes::read, a volumes::write, a volumes::filetree, a volumes::edit or anothervolumes::deleteat the time of the request. - Unsupported. A payload that is the byte
4and 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
5carries 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
0to5, and a payload whose bytes after5are 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,
}
}
}