Response
The server sends the bytes of the file as responses whose first byte is 0, each at most CHUNK_SIZE bytes, and the finish; a file of zero bytes is one empty response; the byte 1, 2 or 3 alone, when a running container, a FUSE serve or an operation holds the volume, or the byte 4 alone, when the server keeps no volume, is the only response; a response whose first byte is 5 carries an error and is the last.
[0][bytes …] a piece of the file
[1] not read: a running container holds the volume
[2] not read: a FUSE serve holds the volume
[3] not read: another operation holds the volume
[4] not read: the server keeps no volume
[5][error JSON …] the file was not read, or not all of it
- The sequence. Zero or more responses whose first byte is
0, each a piece of the file in order, followed by the response finish; or the responses sent so far, exactly one response whose first byte is5, and the response finish; or exactly one response whose payload is the byte1,2,3or4and nothing else, and the response finish. A file of zero bytes is exactly one response whose first byte is0and nothing after it. A response finish that no response precedes states that the request was not served, as Endpoints provides. - The piece. After the byte
0, the bytes of the piece, verbatim, running to the end of the payload. Each piece is at mostCHUNK_SIZEbytes,2 * 1024 * 1024, the constantdiverge-sdk-rs/src/lib.rsdefines; the pieces concatenated are the file, and a receiver measures none. - 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, and sends the one that applies before any piece. On a server that keeps no volume, the byte4is the only response, sent before anything is looked up. Every refusal states that nothing was read and that nothing changed. 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, anothervolumes::read, a volumes::write, a volumes::filetree, a volumes::edit or a volumes::delete at 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 is the last response of the scope. - 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/read/server/response/frame.rs:
//! What a server's response frame carries for a volume read.
use std::fmt;
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};
use crate::shared::containers::read;
use crate::shared::error::Error;
/// A piece of the file, or the news that there will not be one.
///
/// A payload leads with one byte saying which — `0` for
/// [`Body`](Self::Body), `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 the rest is that variant's own bytes,
/// of which the four refusals have none.
///
/// # How it ends
///
/// | the scope ends with | means |
/// |---------------------|-------|
/// | bodies, then a finish | the bytes are the file |
/// | [`Mounted`](Self::Mounted), then a finish | a running container has the volume, and the file was not read |
/// | [`FuseServed`](Self::FuseServed), then a finish | a FUSE serve holds the volume, and the file was not read |
/// | [`Held`](Self::Held), then a finish | another operation has the volume to itself, and the file was not read |
/// | an [`Error`](Self::Error), then a finish | the file was not read, or not all of it |
///
/// Only a finish ends it. A zero-byte file is one empty body, then
/// the finish, so that a finish with nothing before it keeps its
/// standing meaning: could not serve, with nothing to say.
///
/// # Mounted, FUSE served and held are answers, not errors
///
/// A volume that something holds is not touched, and the provider
/// says which thing holds it — a running container, a FUSE serve, or
/// another operation — with its own variant rather than an
/// [`Error`](Self::Error) a caller could not tell from any other
/// failure. The distinction is what a caller acts on: stop the
/// container, stop the FUSE serve, or wait, and ask again.
///
/// # The failure is this scope's, the bytes are not
///
/// [`Body`](Self::Body) carries
/// [`read::response::Frame`](crate::shared::containers::read::response::Frame),
/// which means the same thing wherever the exchange happens — a piece
/// of a file, at most [`CHUNK_SIZE`](crate::CHUNK_SIZE), appended to
/// the pieces before it. The [`Error`](Self::Error) beside it is the
/// exchange's own: a volume the caller cannot see, a path that names
/// no file.
#[derive(Debug, Clone, PartialEq)]
pub enum Frame<'a> {
/// A piece of the file. Tag `0`.
///
/// Each at most [`CHUNK_SIZE`](crate::CHUNK_SIZE), in order, and
/// every one appends to the file.
Body(read::response::Frame<'a>),
/// The volume is mounted in a running container, and was not
/// read. Tag `1`.
///
/// Nothing was changed; a caller stops the container and asks
/// again.
Mounted,
/// A FUSE serve holds the volume, and it was not read.
/// 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
/// read. 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::Body`].
const BODY: 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;
impl Encode for Frame<'_> {
/// The ordinary JSON failure, from the half that has one. The
/// other 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::Body(inner) => {
out.extend_from_slice(&[BODY]);
// Its error is `Infallible`, and an empty match on one
// is how you say so: there is no value to handle.
inner.encode(out).map_err(|error| match error {})
}
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<'a> Decode<'a> for Frame<'a> {
/// 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: &'a [u8]) -> Result<Self, FrameError> {
let (tag, rest) = bytes.split_first().ok_or(FrameError::Empty)?;
match *tag {
BODY => read::response::Frame::decode(rest)
.map(Frame::Body)
.map_err(|error| match error {}),
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 read response frame that could not be read.
#[derive(Debug)]
pub enum FrameError {
/// No bytes at all, so not even a tag.
///
/// Distinct from a zero-length body, which is a tag followed by
/// nothing and is a file.
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 read response frame is empty")
}
FrameError::UnknownTag(tag) => {
write!(f, "unknown volume read response frame tag {tag}")
}
FrameError::Error(error) => {
write!(f, "volume read 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,
}
}
}