# 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.

Canonical: https://protocol.diverge.network/2.3.0/endpoints/images-stat/response/
Specification revision: 2.3.0

The server sends exactly one response on the scope. The response
finish follows the response, and no frame follows the finish.

```text
[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](/2.3.0/endpoints/) provides.
- **The stat.** A payload whose first byte is `0` carries the stat
  after that byte, running to the end of the payload. The stat is the
  `Stat` defined below, encoded as the postcard reading of
  [Notation](/2.3.0/endpoints/#notation) provides: the image as the
  [images::list response](/2.3.0/endpoints/images-list/response/) defines
  it — `name`, `digest`, `bytes`, `created` — followed by
  `containerfile` as 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: the `containerfile` of the client's
  [images::build](/2.3.0/endpoints/images-build/) request, verbatim.
- **Unsupported.** A payload that is the byte `1` and nothing else
  states that the server holds out no
  [`images_store`](/2.3.0/capabilities/#images_store) capability and keeps
  no client image, as [Capabilities](/2.3.0/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 `2` carries 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](/2.3.0/endpoints/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`, `1` and `2`, a payload whose bytes after `0` do not
  decode as the stat, and a payload whose bytes after `2` are not one
  JSON value are malformed.

The response frame is defined by `diverge-sdk-rs/src/provider/endpoints/images/stat/server/response/frame.rs`:

```rust
//! 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](/2.3.0/endpoints/images-list/response/) page:

```rust
//! 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,
}
```
