Response
Responses of the byte 0 or 1 followed by one image in the postcard wire format, added or removed; the byte 2, once, when the listing is whole; the byte 3, alone and first, when the server keeps no client image; or the byte 4 followed by an error as one JSON value, last; and the response finish.
[0][image, postcard …] per image added
[1][image, postcard …] per image removed, after it was added
[2] once, when the listing is whole
[3] once, alone: the server keeps no client image
[4][error JSON …] at most once, last
- The sequence. Zero or more responses of the byte
0followed by one image, one per image the client had built when the scope opened; exactly one response of the byte2and nothing else; then zero or more responses of the byte0or1, each followed by one image, as images are built and deleted; the response finish follows the last of them, when the client stops or the client’s connection ends. Or: the responses sent so far, exactly one response of the byte4followed by an error, and the response finish. Or: exactly one response of the byte3and nothing else, and the response finish. No response follows the error. The server sends no response of the byte1before the byte2. - The image. A payload whose first byte is
0or1carries one image after that byte, running to the end of the payload. The image is theImagedefined below, encoded as the postcard reading of Notation provides: itsnameas a varint byte length followed by that many bytes of UTF-8, followed bydigestas a varint byte length followed by that many bytes of UTF-8, followed bybytesas a varint, followed bycreatedas a varint. - Added. A response of the byte
0states that the image is in the client’s listing. The server sends it exactly once per name while the image is listed: for every image listed when the scope opened, before the byte2; for an image built after, when the server has sent the byte1of its images::build. - Removed. A response of the byte
1states that the image is no longer in the client’s listing, and carries the image as the server sent it as added. The server sends it only for an image it has sent as added on this scope and not since as removed, and exactly once for it. - Whole. A payload that is the byte
2and nothing else states that every image the client had built when the scope opened has been sent as added. A client that wants the listing once reads to this byte and stops. A payload whose first byte is2is read by that byte alone; bytes after it are not read. name. The name of the image, UTF-8: the name the client’s images::build request gave it. The name is the handle of an image in this family: images::stat and images::delete name an image by this string. No two images listed at once share a name.digest. The manifest digest of the image as the server holds it, UTF-8, of the form<algorithm>:<hex>. A container request of the client naming this digest names this image, and the server runs it from what it holds, as the containers::agents::run and containers::tools::run pages state.bytes. The size of the image, in bytes, as the server counts it against the capacity of its store.created. The time at which the build ended, in seconds since1970-01-01T00:00:00Z, unsigned.- Order. The order of the images is not prescribed. A client reads nothing into the position of an image.
- A finish with nothing before it. The request was not served, as Endpoints provides, or the client stopped before the server had sent anything. The server does not say which.
- Unsupported. A payload that is the byte
3and 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
4carries 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
0to4, a payload whose bytes after0or1do not decode as one image, and a payload whose bytes after4are not one JSON value are malformed.
The response is defined by diverge-sdk-rs/src/provider/endpoints/images/list/server/response/frame.rs:
//! What a server's response frame carries for an image listing.
use std::fmt;
use super::Image;
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};
use crate::shared::error::Error;
/// An image the caller built, as it comes to be listed or goes; the
/// word that the listing is whole; or the news that the provider
/// could not say.
///
/// A listing is a stream kept open. First every image the caller has
/// built, each as [`Added`](Self::Added); then exactly one
/// [`Listed`](Self::Listed); then, for the scope's life, an image the
/// caller builds as `Added` and one it deletes as
/// [`Removed`](Self::Removed). Nothing is told as changed: an image
/// never changes. The finish follows the caller's stop, the caller's
/// connection ending, or exactly one [`Error`](Self::Error), and
/// nothing else: a caller with no image is listed as `Listed` at
/// once, and watched. A payload leads with one byte saying which —
/// `0` for `Added`, `1` for `Removed`, `2` for `Listed`, `3` for
/// `Unsupported`, `4` for `Error` — and the rest is that variant's
/// own bytes; `Listed` and `Unsupported` carry nothing. A provider
/// with no image store sends `Unsupported` first and alone, and the
/// finish follows it.
///
/// # A finish with nothing is an answer
///
/// The caller stopped before anything was sent, or the request was
/// not served: a scope that finishes with no response before it is
/// that, not a failure. An [`Error`](Self::Error) is a failure: the
/// provider could not tell.
#[derive(Debug, Clone, PartialEq)]
pub enum Frame {
/// An image the caller built, listed. Tag `0`.
///
/// Once per name while the image is listed: at the opening for
/// every image the caller has then, and after it for one the
/// caller builds. Nothing promises an order.
Added(Image),
/// An image listed already, gone. Tag `1`.
///
/// The image as it was listed.
Removed(Image),
/// Every image the caller had when the scope opened has been
/// listed. Tag `2`.
///
/// Carries nothing — the variant is bare. A caller that wants the
/// listing once stops here; one that watches reads on.
Listed,
/// The provider has no image store — its `images_store` is `false`,
/// see [`info`](crate::provider::endpoints::info) — and nothing was
/// done. Tag `3`.
///
/// Carries nothing. A caller asks another provider; nothing about
/// the request is the cause.
Unsupported,
/// A failure. Tag `4`.
///
/// See [`shared::error::Error`](crate::shared::error::Error) for
/// why it says so little.
Error(Error),
}
/// Tag for [`Frame::Added`].
const ADDED: u8 = 0;
/// Tag for [`Frame::Removed`].
const REMOVED: u8 = 1;
/// Tag for [`Frame::Listed`].
const LISTED: u8 = 2;
/// Tag for [`Frame::Unsupported`].
const UNSUPPORTED: u8 = 3;
/// Tag for [`Frame::Error`].
const ERROR: u8 = 4;
/// Two formats, and the tag chooses between them: an image is
/// postcard, as a volume is in its listing, 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::Added(image) => {
out.extend_from_slice(&[ADDED]);
postcard::to_io(image, &mut *out).map(|_| ()).map_err(FrameEncodeError::Image)
}
Frame::Removed(image) => {
out.extend_from_slice(&[REMOVED]);
postcard::to_io(image, &mut *out).map(|_| ()).map_err(FrameEncodeError::Image)
}
Frame::Listed => {
out.extend_from_slice(&[LISTED]);
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 {
ADDED => postcard::from_bytes(rest).map(Frame::Added).map_err(FrameDecodeError::Image),
REMOVED => postcard::from_bytes(rest).map(Frame::Removed).map_err(FrameDecodeError::Image),
LISTED => Ok(Frame::Listed),
UNSUPPORTED => Ok(Frame::Unsupported),
ERROR => Error::decode(rest).map(Frame::Error).map_err(FrameDecodeError::Error),
tag => Err(FrameDecodeError::UnknownTag(tag)),
}
}
}
/// An image listing response that could not be written.
#[derive(Debug)]
pub enum FrameEncodeError {
/// The image did not serialize.
Image(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::Image(error) => write!(f, "image listing image did not serialize: {error}"),
FrameEncodeError::Error(error) => write!(f, "image listing error did not serialize: {error}"),
}
}
}
impl std::error::Error for FrameEncodeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
FrameEncodeError::Image(error) => Some(error),
FrameEncodeError::Error(error) => Some(error),
}
}
}
/// An image listing 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 five.
UnknownTag(u8),
/// The image did not parse.
Image(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 listing response frame is empty"),
FrameDecodeError::UnknownTag(tag) => write!(f, "unknown image listing response frame tag {tag}"),
FrameDecodeError::Image(error) => write!(f, "image listing image did not parse: {error}"),
FrameDecodeError::Error(error) => write!(f, "image listing error did not parse: {error}"),
}
}
}
impl std::error::Error for FrameDecodeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
FrameDecodeError::Image(error) => Some(error),
FrameDecodeError::Error(error) => Some(error),
FrameDecodeError::Empty | FrameDecodeError::UnknownTag(_) => None,
}
}
}
and by diverge-sdk-rs/src/provider/endpoints/images/list/server/response/image.rs:
//! One image a caller built.
use serde::{Deserialize, Serialize};
/// An image the caller built on the provider, under the name it gave.
///
/// [`name`](Self::name) is what to call it, [`digest`](Self::digest)
/// what it is, [`bytes`](Self::bytes) how big it is, and
/// [`created`](Self::created) when the build ended. That is the whole
/// of what a listing says about one; the Containerfile it was built
/// from is a [`stat`](crate::provider::endpoints::images::stat) away.
///
/// # The digest is how a run names it
///
/// A [`Container`](crate::shared::containers::request::Container)
/// names its image by digest, and this is the digest to name: a run
/// by the identity that built the image, naming it, runs it from the
/// store, with no reference needed and nobody asked. The name is the
/// handle in THIS family — a stat and a delete name the image by it
/// — and nowhere else.
///
/// # Nothing here changes
///
/// An image is built once and never edited, so a listing tells an
/// image added and removed and nothing between. A caller that wants
/// a different image builds one under another name, or deletes and
/// builds again.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Image {
/// What to call this image, and how to ask for it: the name the
/// [`build`](crate::provider::endpoints::images::build) gave it,
/// unique among the caller's images.
pub name: String,
/// 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,
/// When the build ended, in SECONDS since the Unix epoch.
pub created: u64,
}