Response
Zero or more responses of the byte 0 followed by one line of the build’s output; then exactly one response that is the byte 1 followed by the digest and the size of the image, the byte 2 alone, the byte 3 alone when something holds the context volume, the byte 4 alone when the server keeps no client image, or the byte 5 followed by an error as one JSON value; and the response finish.
[0][line_len: varint][line: utf8 …] per line of output
[1][digest_len: varint][digest: utf8 …][bytes: varint] once: the image exists
[2] once: insufficient capacity
[3] once, alone: something holds the context volume
[4] once, alone: the server keeps no client image
[5][error JSON …] once: an error
- The sequence. Zero or more responses of the byte
0, each followed by one line; then exactly one response whose first byte is1,2,3,4or5; then the response finish. No response follows that response. No response of the byte0precedes a response of the byte3or4: each is the only response of its scope. A response finish that no response of the byte1to5precedes states that the request was not served, as Endpoints provides; whether the image exists is then not stated, and the client learns it from an images::list scope. - Output. A payload whose first byte is
0carries one line of the build’s output after that byte, as a varint byte length followed by that many bytes of UTF-8. The server sends the lines in the order the build’s tool wrote them. This revision prescribes nothing about their content. - Built. A payload whose first byte is
1states that the image exists, and carries itsdigestas a varint byte length followed by that many bytes of UTF-8, the manifest digest of the image as the server holds it, followed bybytesas a varint, the size of the image in bytes as the server counts it against its store. The server sends it after the image exists; animages::listscope the client opens after receiving it lists the image, and a list scope the client holds open is sent the image as added. - Insufficient capacity. A payload whose first byte is
2states that the server’s store has no room for the image and that nothing was built. No image exists by that name as a result of the request. The server sends nothing after the byte2, and a client ignores every byte that follows it. - Context held. A payload that is the byte
3and nothing else states that the volume the request names as its context is held at the time of the request — by an operation, by a running container beyond what the volume’s mode object allows withmount_many, or by a FUSE serve beyond what it allows withmount_and_fuse_serve, as Capabilities states for a build’s context — and that nothing was built. No image exists by that name as a result of the request. The server sends it before the build begins, and bytes after it are not read. - Unsupported. A payload that is the byte
4and nothing else states that the server holds out noimages_storecapability and keeps no client image, as Capabilities provides. It is the only response of the scope, sent before anything is looked up; bytes after it are not read. - 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 image does not exist as a result of the request, and that the reason is neither capacity nor the context’s hold. - Malformed. A payload with no byte, a payload whose first byte
is none of
0to5, a payload whose bytes after0do not decode as one string, a payload whose bytes after1do not decode as the digest and the size, and a payload whose bytes after5are not one JSON value are malformed.
The response is defined by diverge-sdk-rs/src/provider/endpoints/images/build/server/response/frame.rs:
//! What a server's response frame carries for an image build.
use std::fmt;
use super::Built;
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};
use crate::shared::error::Error;
/// A line the build said; the image built; the word that the store
/// has no room for it; or the news that the provider could not build
/// it.
///
/// A build is a stream with an end. Zero or more
/// [`Output`](Self::Output) frames, each a line the provider's build
/// tool wrote, as it wrote them; then exactly one of
/// [`Built`](Self::Built), [`InsufficientCapacity`](Self::InsufficientCapacity),
/// [`ContextHeld`](Self::ContextHeld), [`Unsupported`](Self::Unsupported)
/// and [`Error`](Self::Error); then the finish, and nothing after it.
/// A payload leads with one byte saying which — `0` for `Output`,
/// `1` for `Built`, `2` for `InsufficientCapacity`, `3` for
/// `ContextHeld`, `4` for `Unsupported`, `5` for `Error` — and the
/// rest is that variant's own bytes; the three bare refusals carry
/// nothing, and come with no output before them.
///
/// | the scope ends with | means |
/// |----------------------|-------|
/// | [`Built`](Self::Built), then a finish | the image exists under the name, and a listing will show it |
/// | [`InsufficientCapacity`](Self::InsufficientCapacity), then a finish | the store has no room for it, and nothing exists |
/// | [`ContextHeld`](Self::ContextHeld), then a finish | something holds the context volume, and nothing exists |
/// | [`Unsupported`](Self::Unsupported), then a finish | the provider has no image store, and nothing exists |
/// | an [`Error`](Self::Error), then a finish | it does not exist, for some other reason — a build that failed, a name taken, a context refused — and the output before it says what the build said |
///
/// # The refusals are answers, not errors
///
/// A provider whose store has no room for the image says so with
/// [`InsufficientCapacity`](Self::InsufficientCapacity) rather than
/// with an [`Error`](Self::Error) a caller could not tell from any
/// other failure. The distinction is what a caller acts on: a store
/// with no room is one to delete an image from and ask again, and a
/// failure is not. Whether the provider knows before it builds or
/// only after is the provider's; either way nothing exists. A context
/// volume that something holds — a running container, a FUSE serve,
/// or an operation that has it to itself — is
/// [`ContextHeld`](Self::ContextHeld), a thing to stop or wait for;
/// a provider with no image store at all is
/// [`Unsupported`](Self::Unsupported), another provider to ask.
///
/// # Output is text
///
/// Each line is a string, and the provider decodes what its tool
/// wrote before sending it: a caller shows it to a person and does
/// not parse it. Nothing promises what the lines say, how many there
/// are, or that there are any.
#[derive(Debug, Clone, PartialEq)]
pub enum Frame {
/// A line of the build's output, as the build tool wrote it. Tag
/// `0`.
Output(String),
/// The image exists. Tag `1`.
Built(Built),
/// The store has no room for the image, and nothing exists. Tag
/// `2`.
InsufficientCapacity,
/// Something holds the volume the context names — a running
/// container, a FUSE serve, or a stat, a read, a write, a filetree,
/// an edit or a delete that has it to itself — and nothing exists.
/// Tag `3`.
///
/// Carries nothing. A build takes the shared hold a run takes,
/// and a volume that will not take it is this; a caller stops the
/// holder, or waits for it, and asks again.
ContextHeld,
/// The provider has no image store — its `images_store` is `false`,
/// see [`info`](crate::provider::endpoints::info) — and nothing
/// exists. Tag `4`.
///
/// Carries nothing. A caller asks another provider; nothing about
/// the request is the cause.
Unsupported,
/// A failure. Tag `5`.
///
/// See [`shared::error::Error`](crate::shared::error::Error) for
/// why it says so little; the output before it says more.
Error(Error),
}
/// Tag for [`Frame::Output`].
const OUTPUT: u8 = 0;
/// Tag for [`Frame::Built`].
const BUILT: u8 = 1;
/// Tag for [`Frame::InsufficientCapacity`].
const INSUFFICIENT_CAPACITY: u8 = 2;
/// Tag for [`Frame::ContextHeld`].
const CONTEXT_HELD: u8 = 3;
/// Tag for [`Frame::Unsupported`].
const UNSUPPORTED: u8 = 4;
/// Tag for [`Frame::Error`].
const ERROR: u8 = 5;
/// Two formats, and the tag chooses between them: a line and the
/// image built are postcard, as the rest of the store is, and the
/// error is JSON, since a [`serde_json::Value`] cannot come back out
/// of a format with no self-description.
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::Output(line) => {
out.extend_from_slice(&[OUTPUT]);
postcard::to_io(line, &mut *out).map(|_| ()).map_err(FrameEncodeError::Output)
}
Frame::Built(built) => {
out.extend_from_slice(&[BUILT]);
postcard::to_io(built, &mut *out).map(|_| ()).map_err(FrameEncodeError::Built)
}
Frame::InsufficientCapacity => {
out.extend_from_slice(&[INSUFFICIENT_CAPACITY]);
Ok(())
}
Frame::ContextHeld => {
out.extend_from_slice(&[CONTEXT_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 {
/// Five 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 {
OUTPUT => postcard::from_bytes(rest).map(Frame::Output).map_err(FrameDecodeError::Output),
BUILT => postcard::from_bytes(rest).map(Frame::Built).map_err(FrameDecodeError::Built),
INSUFFICIENT_CAPACITY => Ok(Frame::InsufficientCapacity),
CONTEXT_HELD => Ok(Frame::ContextHeld),
UNSUPPORTED => Ok(Frame::Unsupported),
ERROR => Error::decode(rest).map(Frame::Error).map_err(FrameDecodeError::Error),
tag => Err(FrameDecodeError::UnknownTag(tag)),
}
}
}
/// An image build response that could not be written.
#[derive(Debug)]
pub enum FrameEncodeError {
/// The line did not serialize.
Output(postcard::Error),
/// The image built did not serialize.
Built(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::Output(error) => write!(f, "image build output did not serialize: {error}"),
FrameEncodeError::Built(error) => write!(f, "image built did not serialize: {error}"),
FrameEncodeError::Error(error) => write!(f, "image build error did not serialize: {error}"),
}
}
}
impl std::error::Error for FrameEncodeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
FrameEncodeError::Output(error) => Some(error),
FrameEncodeError::Built(error) => Some(error),
FrameEncodeError::Error(error) => Some(error),
}
}
}
/// An image build response 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 line did not parse.
Output(postcard::Error),
/// The image built did not parse.
Built(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("image build response frame is empty"),
FrameDecodeError::UnknownTag(tag) => write!(f, "unknown image build response frame tag {tag}"),
FrameDecodeError::Output(error) => write!(f, "image build output did not parse: {error}"),
FrameDecodeError::Built(error) => write!(f, "image built did not parse: {error}"),
FrameDecodeError::Error(error) => write!(f, "image build error did not parse: {error}"),
}
}
}
impl std::error::Error for FrameDecodeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
FrameDecodeError::Output(error) => Some(error),
FrameDecodeError::Built(error) => Some(error),
FrameDecodeError::Error(error) => Some(error),
FrameDecodeError::Empty | FrameDecodeError::UnknownTag(_) => None,
}
}
}
and the image built by diverge-sdk-rs/src/provider/endpoints/images/build/server/response/built.rs:
//! The image, built.
use serde::{Deserialize, Serialize};
/// The image built: what a listing will report it by, less the name
/// the caller already knows and the time, which is now.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Built {
/// The manifest digest of the image as the provider holds it,
/// `<algorithm>:<hex>`: what a container request names.
pub digest: String,
/// Its size in BYTES, as the provider counts it against the
/// store's capacity.
pub bytes: u64,
}