Request
The payload is the tag byte 14 followed by a JSON object that names the image by manifest digest and by zero or more references, and states which of the two sources the server is to look in.
The payload of the request is the tag byte followed by the request as JSON, running to the end of the payload:
[14][request JSON …]
digest. The manifest digest of the image, of the form<algorithm>:<hex>. The digest identifies the image.references. An array of objects, each of two fields:registry, a registry host with no scheme and no path,docker.io,registry.example.com:5000; andname, a repository path at that registry with no host and no tag,library/nginx. A server fetches a referenced image as<registry>/<name>@<digest>. Absent when there are none.- The references locate. A reference does not identify the
image: every registry that serves the bytes the digest names serves
the same image. The server asks a referenced registry only where
its own policy lists that registry, and ignores a reference naming
one it does not. A reference whose
nameis not a repository path is answered with the error. client. A boolean: whether the server is to look among the images the client built on it. Absent isfalse.registries. A boolean: whether the server is to ask the referenced registries its policy lists. Absent isfalse.- No other field. There is no field that asks the server to look among the images it holds on its own account; the server does not look among them.
- At least one. A request in which neither
clientnorregistriesistrueis answered with the byte2alone, and the server looks nowhere. - Malformed. A payload with no byte, a payload whose first byte
is not
14, and a payload whose bytes after the tag do not parse as the request are malformed. Endpoints states how a server answers a request it cannot read.
The request is defined by
diverge-sdk-rs/src/provider/endpoints/images/check/client/request/frame.rs,
read as Notation provides:
//! What a client's request frame carries for an image check.
use serde::{Deserialize, Serialize};
use super::Sources;
use crate::shared::containers::request::Image;
use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};
/// Ask a provider whether it can supply a particular image: the
/// [`Image`] — its digest, and the references under which its bytes
/// may be fetched — and where to look for it, the [`Sources`].
///
/// The digest is the image's identity and a reference is only a
/// locator — any registry serving these bytes serves the same image,
/// because a client recomputes the hash on pull and rejects a
/// mismatch. The references are where the client knows the bytes to
/// be, and a client that knows of none sends none; a provider asks a
/// referenced registry only where its own policy lists it.
///
/// # Where to look
///
/// Two places, each asked for or not, either or both: the client's
/// own images, built on this provider, and the referenced
/// registries. At least one is asked for, or the provider answers
/// [`NoSource`](crate::provider::endpoints::images::check::server::response::Frame::NoSource)
/// and determines nothing. Available means at least one source asked
/// has the image; the provider looks in the client's own images
/// before it asks a registry. What the provider holds on its own
/// account is not a place a check can look: no image it pulled is
/// revealed by one. See [`Sources`].
///
/// # An image the client built is one the provider holds, for it
///
/// A digest the client [built](crate::provider::endpoints::images::build)
/// on this provider is available to that client, from the store,
/// when the `client` source is asked; to any other client it is not
/// there, unless that client built it too. A provider never answers
/// one identity's store to another.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Frame {
/// The image asked about.
pub image: Image,
/// Where to look: two booleans, flat in the JSON beside
/// `image`.
#[serde(flatten)]
pub sources: Sources,
}
/// This frame's tag among the scope-opening requests.
///
/// One byte at the front of the payload, which is what tells a reader
/// which request it holds. The frame layer does not discriminate them
/// — [`ClientFrame::Request`](crate::wire::frame::client::ClientFrame::Request)
/// is one type carrying bytes — so the distinction has to be in the
/// bytes, and each request owns the value that names it.
///
/// See the table in [`endpoints`](crate::provider::endpoints) for the whole
/// allocation. The values are chosen across modules that do not know
/// about each other, so the table is the only place they can be seen
/// at once.
const TAG: u8 = 14;
impl Encode for Frame {
/// The ordinary JSON failure. The tag cannot fail.
type Error = serde_json::Error;
fn encode(&self, out: &mut Writer<'_>) -> Result<(), Self::Error> {
out.extend_from_slice(&[TAG]);
serde_json::to_writer(out, self)
}
}
impl Decode<'_> for Frame {
/// Three ways to fail, and only one of them is JSON.
type Error = FrameError;
fn decode(bytes: &[u8]) -> Result<Self, Self::Error> {
let (tag, rest) = bytes.split_first().ok_or(FrameError::Empty)?;
if *tag != TAG {
return Err(FrameError::UnexpectedTag(*tag));
}
serde_json::from_slice(rest).map_err(FrameError::Body)
}
}
/// A an image check request request frame that could not be read.
#[derive(Debug)]
pub enum FrameError {
/// No bytes at all, so not even a tag.
Empty,
/// A tag naming some other request.
///
/// A reader that dispatched on the tag will not see this. One that
/// assumed which request it held, and was wrong, will — which is
/// the point of checking a tag rather than skipping it.
UnexpectedTag(u8),
/// The request did not parse.
Body(serde_json::Error),
}
impl std::fmt::Display for FrameError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
FrameError::Empty => {
f.write_str("an image check request request frame is empty")
}
FrameError::UnexpectedTag(tag) => {
write!(f, "expected an image check request request tag {TAG}, found {tag}")
}
FrameError::Body(error) => {
write!(f, "an image check request request did not parse: {error}")
}
}
}
}
impl std::error::Error for FrameError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
FrameError::Body(error) => Some(error),
FrameError::Empty | FrameError::UnexpectedTag(_) => None,
}
}
}