Response
The server sends exactly one response, whose payload begins with the byte 0 followed by the tree in the postcard wire format, a varint count of nodes followed by that many nodes; is the byte 1, 2 or 3 alone when a running container, a FUSE serve or an operation holds the volume; is the byte 4 alone when the server keeps no volume; or begins with 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][tree, postcard …] the tree
[1] not walked: a running container holds the volume
[2] not walked: a FUSE serve holds the volume
[3] not walked: another operation holds the volume
[4] not walked: the server keeps no volume
[5][error JSON …] not walked
- 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.
- The tree. A payload whose first byte is
0carries the tree after that byte, running to the end of the payload: a varint count followed by that many nodes, in the encoding the postcard reading of Notation provides. A node is one of the three variants the filetree channel defines,File,DirectoryorSymlink, in the form that page states, with these determinations. EveryDirectoryhaschanges0: nothing watches the volume, and no change is sent. ASymlinkis the link itself, never followed, and itspathis the target of the link as components relative to the root of the volume.created_atandmodified_atare what the volume records, absent where it records none. The count0is an empty directory, and the payload is exactly the one byte0. - Paths. Every
nameis the name of a node in its directory. The directory the request named has no node of its own: the tree carries its entries. - 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 tree was not sent 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, a volumes::read, a volumes::write, anothervolumes::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. - Malformed. A payload with no byte, a payload whose first byte
is none of
0to5, a payload whose bytes after0do not decode as the tree, and a payload whose bytes after5are not one JSON value are malformed.
The response is defined by diverge-sdk-rs/src/provider/endpoints/volumes/filetree/server/response/frame.rs:
//! What a server's response frame carries for a volume filetree.
use std::fmt;
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};
use crate::shared::error::Error;
use crate::shared::filetree::response::Node;
/// The tree, or the news that it could not be walked.
///
/// One of these on channel `0`, then the scope finishes. A payload
/// leads with one byte saying which — `0` for [`Tree`](Self::Tree),
/// `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.
///
/// # 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.
///
///
/// A filetree here is not a stream: a provider walks the subtree once
/// and says what it found, so there is nothing to discover
/// incrementally and nothing to hold a channel open for. The tree is
/// what a [`Snapshot`](crate::shared::filetree::response::Frame::Snapshot)
/// carries, the entries of the subtree the request named, as
/// [`Node`]s — the same nodes a container's live filetree sends, with
/// every directory's `changes` false, since nothing watches.
#[derive(Debug, Clone, PartialEq)]
pub enum Frame {
/// The tree. Tag `0`.
Tree(Vec<Node>),
/// The volume is mounted in a running container, and was not
/// walked. Tag `1`.
///
/// Nothing was changed; a caller stops the container and asks
/// again.
Mounted,
/// A FUSE serve holds the volume, and it was not walked.
/// 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
/// walked. 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`.
///
/// A volume the caller cannot see is one, and so is a path that
/// names no directory, and a walk the provider could not finish. See
/// [`shared::error::Error`](crate::shared::error::Error) for why
/// it says so little.
Error(Error),
}
/// Tag for [`Frame::Tree`].
const TREE: 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;
/// Two variants, two formats, and the tag chooses between them.
///
/// The tree is **postcard**, as every filetree frame is: the nodes are
/// the same type a container's live filetree sends, and they are
/// written the same way.
///
/// The error is JSON, and has to be. A
/// [`serde_json::Value`] deserializes through `deserialize_any`, which
/// a format with no self-description cannot answer — so postcard can
/// carry the tree and cannot carry the failure, and each variant gets
/// the format it needs.
impl Encode for Frame {
/// One failure per half, and they are different libraries'.
type Error = FrameEncodeError;
// 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<(), FrameEncodeError> {
match self {
Frame::Tree(tree) => {
out.extend_from_slice(&[TREE]);
postcard::to_io(tree, &mut *out)
.map(|_| ())
.map_err(FrameEncodeError::Tree)
}
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).map_err(FrameEncodeError::Error)
}
}
}
}
impl Decode<'_> for Frame {
/// Four ways to fail, and each names which half failed.
type Error = FrameDecodeError;
// Spelled out for the same reason as `encode` above.
fn decode(bytes: &[u8]) -> Result<Self, FrameDecodeError> {
let (tag, rest) = bytes.split_first().ok_or(FrameDecodeError::Empty)?;
match *tag {
TREE => postcard::from_bytes(rest)
.map(Frame::Tree)
.map_err(FrameDecodeError::Tree),
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(FrameDecodeError::Error),
tag => Err(FrameDecodeError::UnknownTag(tag)),
}
}
}
/// A volume filetree that could not be written.
#[derive(Debug)]
pub enum FrameEncodeError {
/// The tree did not serialize.
Tree(postcard::Error),
/// The error did not serialize.
Error(serde_json::Error),
}
impl fmt::Display for FrameEncodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FrameEncodeError::Tree(error) => {
write!(f, "volume filetree did not serialize: {error}")
}
FrameEncodeError::Error(error) => {
write!(f, "volume filetree error did not serialize: {error}")
}
}
}
}
impl std::error::Error for FrameEncodeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
FrameEncodeError::Tree(error) => Some(error),
FrameEncodeError::Error(error) => Some(error),
}
}
}
/// A volume filetree that could not be read.
#[derive(Debug)]
pub enum FrameDecodeError {
/// No bytes at all, so not even a tag.
Empty,
/// A tag that is none of this frame's six.
UnknownTag(u8),
/// The tree did not parse.
Tree(postcard::Error),
/// The error did not parse.
Error(serde_json::Error),
}
impl fmt::Display for FrameDecodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FrameDecodeError::Empty => {
f.write_str("volume filetree response frame is empty")
}
FrameDecodeError::UnknownTag(tag) => {
write!(f, "unknown volume filetree response frame tag {tag}")
}
FrameDecodeError::Tree(error) => {
write!(f, "volume filetree did not parse: {error}")
}
FrameDecodeError::Error(error) => {
write!(f, "volume filetree error did not parse: {error}")
}
}
}
}
impl std::error::Error for FrameDecodeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
FrameDecodeError::Tree(error) => Some(error),
FrameDecodeError::Error(error) => Some(error),
FrameDecodeError::Empty | FrameDecodeError::UnknownTag(_) => None,
}
}
}