Response
The server sends exactly one response, whose payload is exactly the byte 0 when the file is at the path; exactly the byte 1, 2 or 3 when a running container, a FUSE serve or an operation holds the volume; exactly the byte 4 when the server keeps no volume; or the byte 5 followed by an error as one JSON value; once the content channel has finished or the write has failed, or before the content is asked for; 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] written: the file is at the path
[1] not written: a running container holds the volume
[2] not written: a FUSE serve holds the volume
[3] not written: another operation holds the volume
[4] not written: the server keeps no volume
[5][error JSON …] not written, and nothing partial is
- The sequence. Exactly one response precedes the response
finish, and nothing follows the finish. The byte
0is sent only once the content channel has finished; a refusal is sent before the content channel is opened; an error is sent before the content channel is opened, or once the write has failed. A response finish that no response precedes states that the request was not served, as Endpoints provides. - Written. A payload whose first byte is
0states that the file is at the path, whole. 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 nothing was written, that the content was not asked for, and that the destination is as it was. 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, anothervolumes::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 states that the file is not at the path as a result of the request, that nothing partial is, and that the destination is as it was before the request. - 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/write/server/response/frame.rs:
//! What a server's response frame carries for a volume write.
use std::fmt;
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};
use crate::shared::containers::write_path;
use crate::shared::error::Error;
/// The file landed, or it did not.
///
/// A payload leads with one byte saying which — `0` for
/// [`Written`](Self::Written), `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 |
/// |---------------------|-------|
/// | a [`Written`](Self::Written), then a finish | the file is at the path |
/// | [`Mounted`](Self::Mounted), then a finish | a running container has the volume, and nothing was written |
/// | [`FuseServed`](Self::FuseServed), then a finish | a FUSE serve holds the volume, and nothing was written |
/// | [`Held`](Self::Held), then a finish | another operation has the volume to itself, and nothing was written |
/// | an [`Error`](Self::Error), then a finish | it is not, and nothing partial is |
///
/// Only a finish ends it, and it comes only once the content channel
/// has — or at once, for a write refused before the content was asked
/// for: a volume the caller cannot see, one that something holds, a
/// parent that is not a directory.
///
/// # 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 answer is not
///
/// [`Written`](Self::Written) carries
/// [`write_path::response::Frame`](crate::shared::containers::write_path::response::Frame),
/// which means the same thing wherever the exchange happens. The
/// [`Error`](Self::Error) beside it is the exchange's own.
#[derive(Debug, Clone, PartialEq)]
pub enum Frame {
/// The file is at the path. Tag `0`.
Written(write_path::response::Frame),
/// The volume is mounted in a running container, and was not
/// written. Tag `1`.
///
/// Nothing was changed; a caller stops the container and asks
/// again.
Mounted,
/// A FUSE serve holds the volume, and it was not written.
/// 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
/// written. 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::Written`].
const WRITTEN: 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::Written(inner) => {
out.extend_from_slice(&[WRITTEN]);
// 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 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 {
WRITTEN => write_path::response::Frame::decode(rest)
.map(Frame::Written)
.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 write response frame 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 write response frame is empty")
}
FrameError::UnknownTag(tag) => {
write!(f, "unknown volume write response frame tag {tag}")
}
FrameError::Error(error) => {
write!(f, "volume write 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,
}
}
}