# Response

> The server sends exactly one response, whose payload begins with the byte 0 followed by the stat in the postcard wire format or with the byte 1 followed by an error as one JSON value, and the response finish follows it.

Canonical: https://provider.diverge.network/2.3.0/endpoints/volumes-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 …]
[1][error JSON …]
```

- **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: `name` as a varint byte
  length followed by that many bytes of UTF-8, followed by `bytes` as
  a varint, followed by `created` as a varint, followed by
  `bytes_used` as a varint, followed by `dirhash` as a varint byte
  length followed by that many bytes of UTF-8.
- **`name`, `bytes`, `created`.** The volume as the client's listing
  reports it, in the form the
  [volumes::list response](/2.3.0/endpoints/volumes-list/response/) defines.
  The three values equal those the listing reports for the volume at
  the time of the response.
- **`bytes_used`.** The number of bytes of the volume in use, at the
  time of the response.
- **`dirhash`.** The hash of the content of the volume, at the time
  of the response. It is the SHA-256 of the manifest of the volume,
  encoded as base64url without padding. The manifest is one line per
  file of the volume, each line `<hash> <size> <path>`: `<hash>` is
  the SHA-256 of the bytes of the file, encoded as base64url without
  padding; `<size>` is the length of the file in bytes, in decimal;
  `<path>` is the path of the file relative to the root of the
  volume, its components separated by `/`. The lines are sorted
  bytewise. A volume with no file has the manifest of zero bytes, and
  its `dirhash` is `47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU`. Two
  volumes with one `dirhash` have one content; two volumes with
  different `dirhash` values have different content.
- **The error.** A payload whose first byte is `1` 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 neither `0` nor `1`, a payload whose bytes after `0` do not
  decode as the stat, and a payload whose bytes after `1` are not one
  JSON value are malformed.

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

```rust
//! What a server's response frame carries for a volume stat.

use std::fmt;

use super::Stat;
use crate::decode::Decode;
use crate::encode::{Encode, Writer};
use crate::shared::error::Error;

/// The volume 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 [`Error`](Self::Error) — and the rest is that variant's
/// own bytes.
///
/// A stat is not a stream: a provider walks the volume once and says
/// what it found, so there is nothing to discover incrementally and
/// nothing to hold a channel open for.
#[derive(Debug, Clone, PartialEq)]
pub enum Frame {
    /// The volume examined. Tag `0`.
    Stat(Stat),
    /// A failure. Tag `1`.
    ///
    /// A volume the caller cannot see is one, and so is a walk the
    /// provider could not finish. 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::Error`].
const ERROR: u8 = 1;

/// Two variants, two formats, and the tag chooses between them.
///
/// The stat is **postcard**, matching the rest of
/// [`volumes`](crate::endpoints::volumes): this relays nothing, so no
/// byte of it has to survive a round trip unchanged, and nothing
/// downstream reads it as text.
///
/// The error is JSON, and has to be. A
/// [`serde_json::Value`] deserializes through `deserialize_any`, which
/// a format with no self-description cannot answer — so postcard can
/// carry the stat and cannot carry the failure, and each variant gets
/// the format it needs.
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::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),
            ERROR => Error::decode(rest)
                .map(Frame::Error)
                .map_err(FrameDecodeError::Error),
            tag => Err(FrameDecodeError::UnknownTag(tag)),
        }
    }
}

/// A volume 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, "volume stat did not serialize: {error}")
            }
            FrameEncodeError::Error(error) => {
                write!(f, "volume 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),
        }
    }
}

/// A volume 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 neither of this frame's two.
    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("volume stat response frame is empty")
            }
            FrameDecodeError::UnknownTag(tag) => {
                write!(f, "unknown volume stat response frame tag {tag}")
            }
            FrameDecodeError::Stat(error) => {
                write!(f, "volume stat did not parse: {error}")
            }
            FrameDecodeError::Error(error) => {
                write!(f, "volume 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-provider-sdk/src/endpoints/volumes/stat/server/response/stat.rs`. Its `volume`
field is the `Volume` defined on the
[volumes::list response](/2.3.0/endpoints/volumes-list/response/) page:

```rust
//! One volume, examined.

use serde::{Deserialize, Serialize};

use crate::endpoints::volumes::list::server::response::Volume;

/// What a listing says about a volume, and the two things it does not.
///
/// [`volume`](Self::volume) is the listing's own record of it, the same
/// three fields a [`list`](crate::endpoints::volumes::list) reports;
/// [`bytes_used`](Self::bytes_used) and [`dirhash`](Self::dirhash) are
/// what a listing leaves out, because each costs a walk of the volume
/// and a stat pays for one volume's walk on purpose.
///
/// # Nested rather than flattened
///
/// The listing's [`Volume`] 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`, `bytes`, `created`, then the two
/// fields of its own — a listing's record with two fields appended.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub struct Stat {
    /// The volume as a listing reports it: its name, its size, and
    /// when it came into being.
    pub volume: Volume,
    /// How much of it is in use, in BYTES.
    ///
    /// The one field of a volume that changes on its own: writing
    /// inside the volume moves it, and nothing in this protocol
    /// reports when. It is what a stat was asked for, as of the
    /// moment the provider walked the volume.
    pub bytes_used: u64,
    /// The hash of the volume's content, at the time of the stat.
    ///
    /// The base64url SHA-256, unpadded, of the volume's manifest: one
    /// `<hash> <size> <path>` line per file, `<hash>` the base64url
    /// SHA-256 of the file's bytes, `<size>` its length in bytes,
    /// `<path>` relative to the volume's root and `/`-separated, the
    /// lines sorted bytewise. It is the hash half of the directory
    /// identity an
    /// [`IdentityMount`](crate::shared::containers::request::IdentityMount)
    /// carries, without the size — the listing already reports size.
    ///
    /// A volume with no file has the hash of the empty manifest,
    /// `47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU`, which is what a
    /// [`create`](crate::endpoints::volumes::create) just made.
    ///
    /// # Why a hash rather than a version
    ///
    /// Two stats with one `dirhash` have one content between them;
    /// two with different ones do not. That is the whole of what it
    /// says, and a counter could not say it across providers.
    pub dirhash: String,
}
```
