/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 with | Meaning |
|---|---|
| one or more bodies and the close | the bytes of the bodies, concatenated, are the file |
| an error and the close | the file was not read, or was not read whole |
| the close and no message before it | the read was not served |
- Pieces. Each body carries at most
2097152bytes (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 {}