Response
The server sends exactly one response, whose payload begins with the byte 0 followed by the answer, with the byte 1 followed by an error, or is the byte 2 alone when the request named no source the server has, 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][answer JSON …]
[1][error JSON …]
[2]
- 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 answer. A payload whose first byte is
0carries the answer as JSON after that byte. The answer is exactly one of two objects:{"type":"available"}or{"type":"unavailable"}. The available answer states that at least one source the request asked for has the image, at the time of the answer, for a container request naming the requested digest: the client’s own images, built on the server by images::build and no other client’s, whereclientwas asked and the server holds out theimages_storecapability; a referenced registry in the server’simages_registries, whereregistrieswas asked. The server does not answer available on account of an image it holds on its own account. It reserves nothing, and a later container request naming the image may fail. The unavailable answer states that no source asked has it; a container request naming the image may still succeed where the client holds the image, or where a source not asked has it. It states no reason and names no source: an image the server cannot obtain and an image the server could obtain and will not serve to this client receive the same answer. - The error. A payload whose first byte is
1carries an error as JSON after that byte. The error states that the server did not answer the question and that nothing was determined about the image. An error is not an unavailable answer. - No source. A payload that is the byte
2and nothing else states that the request asked for no source the server has — it asked for neither source, or only forclienton a server that holds out noimages_storecapability, or only forregistrieson a server whoseimages_registriesis empty, or for both on a server that has neither — that the server looked nowhere, and that nothing is determined about the image. A payload whose first byte is2is read by that byte alone; bytes after it are not read. - Malformed. A payload with no byte, a payload whose first byte
is none of
0,1and2, and a payload whose bytes after0or1do not parse as the answer or as the error are malformed.
The response frame is defined by
diverge-sdk-rs/src/provider/endpoints/images/check/server/response/frame.rs:
//! What a server's response frame carries in an image check.
use std::fmt;
use super::Response;
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};
use crate::shared::error::Error;
/// The payload of a [`ServerFrame::Response`](crate::wire::frame::server::ServerFrame::Response)
/// on channel `0` of an image check.
///
/// A check is one question and one reply, so there is exactly one of
/// these per scope, before the finish that ends it. There are three
/// things that reply can be: an answer, the news that the question
/// could not be answered, or the word that the question named no
/// source the provider has to look in.
///
/// A payload leads with one byte saying which — `0` for
/// [`Response`](Self::Response), `1` for [`Error`](Self::Error), `2`
/// for [`NoSource`](Self::NoSource) — and the rest is that variant's
/// own JSON; `NoSource` carries nothing.
///
/// | the scope ends with | means |
/// |----------------------|-------|
/// | a [`Response`](Self::Response), then a finish | the provider looked where it was asked to, and this is what it found |
/// | an [`Error`](Self::Error), then a finish | the provider could not look, and nothing is determined |
/// | [`NoSource`](Self::NoSource), then a finish | the request named no source the provider has, and nothing is determined |
///
/// # The frame type is still not a wire shape
///
/// It carries no serde derives, and it is not what gets serialized —
/// it is the DISPATCH layer, saying which payload a response frame
/// holds. What changed is that the choice is now written down: it used
/// to be settled entirely by the scope the frame arrived in, and one
/// byte at the front now settles it instead.
///
/// That byte is not decoration. [`Response`](super::Response) is
/// untagged and tells its two answers apart by a `type` constant
/// inside each one, while an [`Error`] is an arbitrary JSON value —
/// including, legitimately, an object with a `type` field. Leaving the
/// two to be distinguished by their JSON would mean a provider's error
/// text could be read as an answer, and "I could not tell you" would
/// arrive looking like "no".
///
/// # An error is not an unavailable
///
/// They are the two things this scope can end with and they mean
/// opposite things about the image.
///
/// [`Unavailable`](super::Unavailable) is an ANSWER: the provider
/// looked and will not supply it. It says nothing about why, on
/// purpose — distinguishing "I do not have it" from "I will not serve
/// it to you" would tell an unauthorized caller that a private image
/// exists.
///
/// An [`Error`](Self::Error) is the absence of an answer. Nothing was
/// determined about the image, and a caller that treats one as the
/// other either gives up on an image it could have had or retries
/// forever against one it never will.
#[derive(Debug, Clone, PartialEq)]
pub enum Frame {
/// The answer to the check. Tag `0`.
Response(Response),
/// A failure. Tag `1`.
///
/// See [`shared::error::Error`](crate::shared::error::Error) for
/// why it says so little.
Error(Error),
/// The request named no source the provider has, and nothing was
/// looked at. Tag `2`.
///
/// Neither an answer nor a failure: the question was empty, for
/// this provider. A request asking for neither source is this; so
/// is one asking `client` of a provider with no image store, or
/// `registries` of one that lists no registry — see
/// [`info`](crate::provider::endpoints::info) for both — and
/// nothing else. A caller that wanted an answer asks again with a
/// source the provider has set, or asks another provider.
NoSource,
}
/// Tag for [`Frame::Response`].
const RESPONSE: u8 = 0;
/// Tag for [`Frame::Error`].
const ERROR: u8 = 1;
/// Tag for [`Frame::NoSource`].
const NO_SOURCE: u8 = 2;
/// A tag, then the variant's own JSON. The newtype around an answer
/// still leaves no trace, and neither does the untagged enum inside
/// it — what a response encodes to is exactly what it encoded to
/// before the tag existed.
impl Encode for Frame {
/// The ordinary JSON failure, from whichever half is present.
type Error = serde_json::Error;
// 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<(), serde_json::Error> {
match self {
Frame::Response(response) => {
out.extend_from_slice(&[RESPONSE]);
serde_json::to_writer(out, response)
}
Frame::Error(error) => {
out.extend_from_slice(&[ERROR]);
error.encode(out)
}
Frame::NoSource => {
out.extend_from_slice(&[NO_SOURCE]);
Ok(())
}
}
}
}
impl Decode<'_> for Frame {
/// Four ways to fail, and each names which half failed.
type Error = FrameError;
// Spelled out for the same reason as `encode` above.
fn decode(bytes: &[u8]) -> Result<Self, FrameError> {
let (tag, rest) = bytes.split_first().ok_or(FrameError::Empty)?;
match *tag {
RESPONSE => serde_json::from_slice(rest)
.map(Frame::Response)
.map_err(FrameError::Response),
ERROR => {
Error::decode(rest).map(Frame::Error).map_err(FrameError::Error)
}
NO_SOURCE => Ok(Frame::NoSource),
tag => Err(FrameError::UnknownTag(tag)),
}
}
}
/// An image check response frame that could not be read.
#[derive(Debug)]
pub enum FrameError {
/// No bytes at all, so not even a tag.
Empty,
/// A tag that is none of this frame's three.
UnknownTag(u8),
/// The answer did not parse.
///
/// [`Response`](super::Response) is untagged, so a payload that is
/// neither an available nor an unavailable answer fails here
/// rather than decoding as something half-right.
Response(serde_json::Error),
/// The error did not parse.
///
/// Which is its own small joke and its own real problem: a
/// provider whose failure report is malformed has told a caller
/// that something went wrong and nothing else.
Error(serde_json::Error),
}
impl fmt::Display for FrameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FrameError::Empty => {
f.write_str("image check response frame is empty")
}
FrameError::UnknownTag(tag) => {
write!(f, "unknown image check response frame tag {tag}")
}
FrameError::Response(error) => {
write!(f, "image check answer did not parse: {error}")
}
FrameError::Error(error) => {
write!(f, "image check error did not parse: {error}")
}
}
}
}
impl std::error::Error for FrameError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
FrameError::Response(error) | FrameError::Error(error) => {
Some(error)
}
FrameError::Empty | FrameError::UnknownTag(_) => None,
}
}
}
The answer is defined by
diverge-sdk-rs/src/provider/endpoints/images/check/server/response/response.rs,
read as Notation provides: an enum marked
untagged is exactly one of its variants’ shapes, and an enum of one
variant is a constant.
//! Whether the image can be supplied.
use serde::{Deserialize, Serialize};
use super::{Available, Unavailable};
/// Whether a provider can supply the image that was asked about.
///
/// Untagged, with each variant's payload carrying its own `type`
/// constant — the same discipline the agentic loop chunks use. serde
/// has no tag of its own to read, so the answer goes on the wire as
/// itself rather than as a wrapper around itself.
///
/// Two variants rather than a `bool`, because only one of the two
/// answers has anything more to say. An available image has terms
/// attached to it; an unavailable one is just absent. A boolean would
/// force everything that qualifies availability to sit beside it as an
/// optional field that is meaningless when the answer is no.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum Response {
/// The provider can supply it.
Available(Available),
/// It cannot.
Unavailable(Unavailable),
}
Its two shapes are defined by
diverge-sdk-rs/src/provider/endpoints/images/check/server/response/available.rs
and
diverge-sdk-rs/src/provider/endpoints/images/check/server/response/unavailable.rs:
//! The affirmative answer.
use serde::{Deserialize, Serialize};
/// The provider can supply the image.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub struct Available {
/// The discriminator.
pub r#type: AvailableType,
}
/// [`Available`]'s discriminator.
///
/// One variant, and the reason [`Response`](super::Response) can be
/// untagged: [`Unavailable`](super::Unavailable) cannot produce this
/// value.
#[derive(
Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize,
)]
pub enum AvailableType {
#[serde(rename = "available")]
#[default]
Available,
}
//! The negative answer.
use serde::{Deserialize, Serialize};
/// The provider cannot supply the image.
///
/// Deliberately says nothing about why. "I do not have it" and "I have
/// it but will not serve it to you" are the same answer from where the
/// caller stands, and distinguishing them would tell an unauthorized
/// caller that a private image exists.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub struct Unavailable {
/// The discriminator.
pub r#type: UnavailableType,
}
/// [`Unavailable`]'s discriminator.
///
/// One variant, and the reason [`Response`](super::Response) can be
/// untagged: [`Available`](super::Available) cannot produce this
/// value.
#[derive(
Debug, Clone, Copy, Default, PartialEq, Eq, Hash, Serialize, Deserialize,
)]
pub enum UnavailableType {
#[serde(rename = "unavailable")]
#[default]
Unavailable,
}