Response
The server sends exactly one response, whose payload begins with the byte 0 followed by the stat in the postcard wire format, is the byte 1 alone when the server keeps no client image, or begins with the byte 2 followed by an error as one JSON value, and the response finish follows it.
The server sends exactly one response on the scope. The response finish follows the response, and no frame follows the finish.
[0][stat, postcard …] the image, examined
[1] not examined: the server keeps no client image
[2][error JSON …] not examined
- The sequence. Exactly one response precedes the response finish, and nothing follows the finish. A response finish that no response precedes states that the request was not served, as Endpoints provides.
- The stat. A payload whose first byte is
0carries the stat after that byte, running to the end of the payload. The stat is theStatdefined below, encoded as the postcard reading of Notation provides: the image as the images::list response defines it —name,digest,bytes,created— followed bycontainerfileas a varint byte length followed by that many bytes of UTF-8. name,digest,bytes,created. The image as the client’s listing reports it. The four values equal those the listing reports for the image at the time of the response.containerfile. The Containerfile the image was built from, UTF-8: thecontainerfileof the client’s images::build request, verbatim.- Unsupported. A payload that is the byte
1and 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
2carries 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
0,1and2, a payload whose bytes after0do not decode as the stat, and a payload whose bytes after2are not one JSON value are malformed.
The response frame is defined by diverge-sdk-rs/src/provider/endpoints/images/stat/server/response/frame.rs:
//! What a server's response frame carries for an image stat.
use std::fmt;
use super::Stat;
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};
use crate::shared::error::Error;
/// The image examined, or the news that it could not be.
///
/// One of these on channel `0`, then the scope finishes. A payload
/// leads with one byte saying which — `0` for [`Stat`](Self::Stat),
/// `1` for [`Unsupported`](Self::Unsupported), `2` for
/// [`Error`](Self::Error) — and the rest is that variant's own
/// bytes, of which `Unsupported` has none.
#[derive(Debug, Clone, PartialEq)]
pub enum Frame {
/// The image examined. Tag `0`.
Stat(Stat),
/// The provider has no image store — its `images_store` is `false`,
/// see [`info`](crate::provider::endpoints::info) — and nothing was
/// done. Tag `1`.
///
/// Carries nothing. A caller asks another provider; nothing about
/// the request is the cause.
Unsupported,
/// A failure. Tag `2`.
///
/// An image the caller has none by that name is one, and so is a
/// provider that could not look. See
/// [`shared::error::Error`](crate::shared::error::Error) for why
/// it says so little.
Error(Error),
}
/// Tag for [`Frame::Stat`].
const STAT: u8 = 0;
/// Tag for [`Frame::Unsupported`].
const UNSUPPORTED: u8 = 1;
/// Tag for [`Frame::Error`].
const ERROR: u8 = 2;
/// Two variants, two formats, and the tag chooses between them: the
/// stat is 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::Stat(stat) => {
out.extend_from_slice(&[STAT]);
postcard::to_io(stat, &mut *out).map(|_| ()).map_err(FrameEncodeError::Stat)
}
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 {
STAT => postcard::from_bytes(rest).map(Frame::Stat).map_err(FrameDecodeError::Stat),
UNSUPPORTED => Ok(Frame::Unsupported),
ERROR => Error::decode(rest).map(Frame::Error).map_err(FrameDecodeError::Error),
tag => Err(FrameDecodeError::UnknownTag(tag)),
}
}
}
/// An image stat that could not be written.
#[derive(Debug)]
pub enum FrameEncodeError {
/// The stat did not serialize.
Stat(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::Stat(error) => write!(f, "image stat did not serialize: {error}"),
FrameEncodeError::Error(error) => write!(f, "image stat error did not serialize: {error}"),
}
}
}
impl std::error::Error for FrameEncodeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
FrameEncodeError::Stat(error) => Some(error),
FrameEncodeError::Error(error) => Some(error),
}
}
}
/// An image stat 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 three.
UnknownTag(u8),
/// The stat did not parse.
Stat(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 stat response frame is empty"),
FrameDecodeError::UnknownTag(tag) => write!(f, "unknown image stat response frame tag {tag}"),
FrameDecodeError::Stat(error) => write!(f, "image stat did not parse: {error}"),
FrameDecodeError::Error(error) => write!(f, "image stat error did not parse: {error}"),
}
}
}
impl std::error::Error for FrameDecodeError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
FrameDecodeError::Stat(error) => Some(error),
FrameDecodeError::Error(error) => Some(error),
FrameDecodeError::Empty | FrameDecodeError::UnknownTag(_) => None,
}
}
}
The stat is defined by diverge-sdk-rs/src/provider/endpoints/images/stat/server/response/stat.rs. Its image
field is the Image defined on the
images::list response page:
//! What a stat says about an image.
use serde::{Deserialize, Serialize};
use crate::provider::endpoints::images::list::server::response::Image;
/// What a listing says about an image, and what it was built from.
///
/// # Nested rather than flattened
///
/// The listing's [`Image`] sits inside this as a field rather than
/// having its fields copied in, so there is one definition of what a
/// listing says. Postcard writes a nested struct as its fields in
/// place, so the wire reads `name`, `digest`, `bytes`, `created`,
/// then the one field of its own — a listing's record with one field
/// appended.
///
/// # Why the Containerfile is not in the listing
///
/// Because it is as long as its author made it, and a listing that
/// carried every image's Containerfile would carry the ones nobody
/// asked about. A caller pays for it here, one image at a time.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Stat {
/// The image as a listing reports it: its name, its digest, its
/// size, and when it was built.
pub image: Image,
/// The Containerfile the image was built from, verbatim, as the
/// [`build`](crate::provider::endpoints::images::build) request
/// carried it.
pub containerfile: String,
}