/filesystem/read

One file out of the container: the server sends exactly one message naming the file; the proxy sends the bytes of the file in one or more messages and closes the connection, or sends an error as its last message.

The server opens /filesystem/read and sends exactly one message, the request defined below as JSON, naming the file. The proxy sends the bytes of the file and closes the connection.

server → proxy:   [request JSON]                        once
proxy → server:   [0][bytes …] … [0][bytes …]           the file
                  [1][message …]                        last, when the read failed
The path ends withMeaning
one or more bodies and the closethe bytes of the bodies, concatenated, are the file
an error and the closethe file was not read, or was not read whole
the close and no message before itthe read was not served
  • Pieces. Each body carries at most 2097152 bytes (2 MiB). The proxy sends a file of zero bytes as exactly one empty body before the close.
  • A file, never a directory. A path that names a directory is answered with an error.
  • No repetition. An abrupt end of the path is a read that failed. No party retries it.

The request is defined by diverge-provider-sdk/src/shared/containers/read/request/request.rs:

//! One file to read.

use serde::{Deserialize, Serialize};

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

/// Read one file out of the container.
///
/// No offset and no length. A read starts at the beginning and runs to
/// whatever end it finds, because a file being written to has no
/// stable size to address into — see
/// [`response::Frame`](super::super::response::Frame) for what that
/// costs and how it is reported.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub struct Request {
    /// The file, as path components from the container's root.
    ///
    /// The same meaning of "path" as everywhere else in this API, and
    /// the same frame of reference a
    /// [`filetree`](crate::shared::filetree) stream uses — so a caller
    /// watching a container reads a file by handing back the path the
    /// watch just named.
    ///
    /// Components rather than a joined string: a path is a sequence,
    /// and joining it would invent a separator that then has to be
    /// escaped out of names containing it.
    pub path: Vec<String>,
}

impl Encode for Request {
    /// The ordinary JSON failure.
    type Error = serde_json::Error;

    fn encode(&self, out: &mut Writer<'_>) -> Result<(), Self::Error> {
        serde_json::to_writer(out, self)
    }
}

impl Decode<'_> for Request {
    /// The ordinary JSON failure.
    type Error = serde_json::Error;

    fn decode(bytes: &[u8]) -> Result<Self, Self::Error> {
        serde_json::from_slice(bytes)
    }
}

The messages are defined by diverge-provider-sdk/src/container_proxy/filesystem/read/response/frame.rs:

//! A piece of the file, or why there will not be one.

use std::convert::Infallible;

use super::FrameError;
use crate::encode::{Encode, Writer};
use crate::shared::containers::read;

/// One message on `/filesystem/read`.
///
/// ```text
/// [kind: u8][bytes… | message…]
/// ```
///
/// A body is the shared read's own piece —
/// [`read::response::Frame`](crate::shared::containers::read::response::Frame),
/// bytes and nothing else — behind a kind byte, so an error can sit
/// beside it. The byte per chunk is the price of a read that can say
/// why it stopped, and it is small next to the chunk.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum Frame<'a> {
    /// Kind `0`. A piece of the file, in order. Empty is a piece: an
    /// empty file is one of these.
    Body(read::response::Frame<'a>),
    /// Kind `1`. The file was not read, or not all of it, and this
    /// says why, for a reader rather than a program: no such file, a
    /// directory, a read that failed partway. The last message before
    /// the close. Nothing here is enumerated, because what a
    /// filesystem refuses is its own business.
    Error(&'a str),
}

impl Encode for Frame<'_> {
    /// [`Infallible`]: a kind byte and bytes copied.
    type Error = Infallible;

    fn encode(&self, out: &mut Writer<'_>) -> Result<(), Infallible> {
        match self {
            Frame::Body(body) => {
                out.extend_from_slice(&[0]);
                body.encode(out)
            }
            Frame::Error(message) => {
                out.extend_from_slice(&[1]);
                out.extend_from_slice(message.as_bytes());
                Ok(())
            }
        }
    }
}

impl<'a> Frame<'a> {
    /// Decode one message. The body or the message borrows from
    /// `bytes`.
    pub fn decode(bytes: &'a [u8]) -> Result<Self, FrameError> {
        let (kind, rest) = bytes.split_first().ok_or(FrameError::Empty)?;
        match *kind {
            0 => Ok(Frame::Body(read::response::Frame(rest))),
            1 => std::str::from_utf8(rest)
                .map(Frame::Error)
                .map_err(|_| FrameError::MessageUtf8),
            other => Err(FrameError::UnknownKind(other)),
        }
    }
}

The ways in which a message fails to decode are defined by diverge-provider-sdk/src/container_proxy/filesystem/read/response/error.rs:

//! Why a message on `/filesystem/read` could not be decoded.

use std::error;
use std::fmt;

/// A read answer that could not be read.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum FrameError {
    /// No bytes at all, so not even a kind.
    ///
    /// Distinct from an empty body, which is a kind byte followed by
    /// nothing and is an empty file's one message.
    Empty,
    /// A kind this answer does not define.
    UnknownKind(u8),
    /// An error message that is not UTF-8.
    MessageUtf8,
}

impl fmt::Display for FrameError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            FrameError::Empty => f.write_str("read answer is empty"),
            FrameError::UnknownKind(kind) => {
                write!(f, "unknown read answer kind {kind}")
            }
            FrameError::MessageUtf8 => {
                f.write_str("read error message is not utf-8")
            }
        }
    }
}

impl error::Error for FrameError {}