Request

The payload is the tag byte 5 followed by the name of the volume as a varint length and UTF-8 bytes, in the postcard wire format.

The payload of the request is the tag byte followed by the request in the postcard wire format, as the postcard reading of Notation provides:

[5][name_len: varint][name: utf8 …]
  • name. The name of the volume, UTF-8, preceded by its byte length as a varint. It is the name of a volume in the client’s volumes::list listing.
  • Bytes after the request. A server ignores every byte that follows the name field. A request whose payload extends past the request is not malformed.
  • Malformed. A payload with no byte, a payload whose first byte is not 5, and a payload whose bytes after the tag do not decode as the request are malformed. Endpoints states how a server answers a request it cannot read.

The request is defined by diverge-provider-sdk/src/endpoints/volumes/watch/client/request/frame.rs:

//! What a client's request frame carries for a watch.

use serde::{Deserialize, Serialize};

use crate::decode::Decode;
use crate::encode::{Encode, Writer};

/// Watch one of the volumes a provider offers.
///
/// # A name, not a path
///
/// The one field is a
/// [`Volume::name`](crate::endpoints::volumes::list::server::response::Volume::name)
/// from a listing, and this is the whole of the access model. A caller
/// cannot watch a volume it was not offered, cannot escape one by
/// naming components above it, and cannot probe for what exists by
/// watching and reading the error — because a path it invents is not
/// something this request can express.
///
/// A provider therefore never has to validate a path, only look up a
/// name it published. That is a smaller job and a much smaller
/// mistake to make.
///
/// # What comes back
///
/// A [`filetree`](crate::shared::filetree) stream on channel `0`: one snapshot
/// carrying the whole tree, then one frame per change for as long as
/// the scope lives. Every path in it is relative to the volume
/// named here.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub struct Frame {
    /// Which volume, by the name a listing gave it.
    ///
    /// Names come from
    /// [`Volume::name`](crate::endpoints::volumes::list::server::response::Volume::name)
    /// and mean nothing outside the provider that published them.
    pub name: String,
}

/// This frame's tag among the scope-opening requests.
///
/// One byte at the front of the payload, which is what tells a reader
/// which request it holds. The frame layer does not discriminate them
/// — [`ClientFrame::Request`](crate::frame::client::ClientFrame::Request)
/// is one type carrying bytes — so the distinction has to be in the
/// bytes, and each request owns the value that names it.
///
/// See the table in [`endpoints`](crate::endpoints) for the whole
/// allocation. The values are chosen across modules that do not know
/// about each other, so the table is the only place they can be seen
/// at once.
const TAG: u8 = 5;

/// Postcard, matching the rest of [`volumes`](crate::endpoints::volumes)
/// and the [`filetree`](crate::shared::filetree) stream this opens.
impl Encode for Frame {
    /// Postcard's own failure. The tag cannot fail.
    type Error = postcard::Error;

    fn encode(&self, out: &mut Writer<'_>) -> Result<(), Self::Error> {
        out.extend_from_slice(&[TAG]);
        postcard::to_io(self, &mut *out)?;
        Ok(())
    }
}

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

    fn decode(bytes: &[u8]) -> Result<Self, Self::Error> {
        let (tag, rest) = bytes.split_first().ok_or(FrameError::Empty)?;
        if *tag != TAG {
            return Err(FrameError::UnexpectedTag(*tag));
        }
        postcard::from_bytes(rest).map_err(FrameError::Body)
    }
}

/// A watch request frame that could not be read.
#[derive(Debug)]
pub enum FrameError {
    /// No bytes at all, so not even a tag.
    Empty,
    /// A tag naming some other request.
    ///
    /// A reader that dispatched on the tag will not see this. One that
    /// assumed which request it held, and was wrong, will — which is
    /// the point of checking a tag rather than skipping it.
    UnexpectedTag(u8),
    /// The request did not parse.
    Body(postcard::Error),
}

impl std::fmt::Display for FrameError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            FrameError::Empty => {
                f.write_str("watch request frame is empty")
            }
            FrameError::UnexpectedTag(tag) => {
                write!(f, "expected watch request tag {TAG}, found {tag}")
            }
            FrameError::Body(error) => {
                write!(f, "watch request did not parse: {error}")
            }
        }
    }
}

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