# Request

> The payload is the tag byte 17 followed by the name, the Containerfile and the optional context, in the postcard wire format.

Canonical: https://protocol.diverge.network/2.3.0/endpoints/images-build/request/
Specification revision: 2.3.0

The payload of the request is the tag byte followed by the request in
the postcard wire format, as the postcard reading of
[Notation](/2.3.0/endpoints/#notation) provides:

```text
[17][name_len: varint][name: utf8 …][containerfile_len: varint][containerfile: utf8 …][0]
[17][name_len: varint][name: utf8 …][containerfile_len: varint][containerfile: utf8 …][1][volume_name_len: varint][volume_name: utf8 …][components: varint][component_len: varint][component: utf8 …]…
```

- **`name`.** The name of the image, UTF-8, preceded by its byte
  length as a varint. The client chooses it.
- **`containerfile`.** The Containerfile, UTF-8, preceded by its byte
  length as a varint. The server builds from it verbatim.
- **`context`.** One byte: `0`, the build has no context; or `1`,
  followed by the context. The context is the `Context` defined
  below: `volume_name` as a varint byte length followed by that many
  bytes of UTF-8, the `name` of a volume in the client's
  [volumes::list](/2.3.0/endpoints/volumes-list/) listing; followed by
  `volume_relative_path` as a varint count of components followed by
  each component as a varint byte length and that many bytes of
  UTF-8, the path of the directory relative to the volume, empty for
  the volume itself. A component that is empty, `.` or `..` is
  answered with an error. The context carries no mode: a build writes
  nothing into it.
- **Bytes after the request.** A server ignores every byte that
  follows the `context` field. A request whose payload extends past
  the request is not malformed.
- **Malformed.** A payload with no byte, a payload whose first byte
  is not `17`, and a payload whose bytes after the tag do not decode
  as the request are malformed. [Endpoints](/2.3.0/endpoints/) states how a
  server answers a request it cannot read.

The request is defined by `diverge-sdk-rs/src/provider/endpoints/images/build/client/request/frame.rs`:

```rust
//! What a client's request frame carries for an image build.

use serde::{Deserialize, Serialize};

use super::Context;

use crate::wire::decode::Decode;
use crate::wire::encode::{Encode, Writer};

/// Build an image on the provider, from a Containerfile, under a
/// name.
///
/// # The name is the caller's, and has to stay unique
///
/// Nothing derives it from anything and nothing constrains it here.
/// What a provider will accept as a name is the provider's to state,
/// and a caller learns it by being refused. A name the caller already
/// has an image by is refused; a caller that wants the name again
/// deletes the image first.
///
/// # The Containerfile is the whole recipe
///
/// Its text, verbatim, as the provider's build tool reads one. What
/// it may `FROM` is what the provider can obtain, by its own policy;
/// what it may `COPY` is the context, which is the volume directory
/// [`context`](Self::context) names, or nothing. A build reads its
/// context and writes nothing into it: a Containerfile copies in and
/// never out.
///
/// # What a build answers
///
/// The provider's build tool says things as it goes, and the caller
/// hears them: zero or more
/// [`Output`](crate::provider::endpoints::images::build::server::response::Frame::Output)
/// lines, then exactly one of
/// [`Built`](crate::provider::endpoints::images::build::server::response::Frame::Built),
/// [`InsufficientCapacity`](crate::provider::endpoints::images::build::server::response::Frame::InsufficientCapacity)
/// and [`Error`](crate::provider::endpoints::images::build::server::response::Frame::Error).
/// A build that fails says why in its output, and the error after it
/// says only that it failed.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub struct Frame {
    /// What to call the image, once built.
    pub name: String,
    /// The Containerfile, verbatim.
    pub containerfile: String,
    /// The directory of one of the caller's volumes that is the
    /// build's context and working directory, or none: see
    /// [`Context`]. With none, the build has no context, and a
    /// Containerfile that copies from one fails. One postcard byte,
    /// `0` for none and `1` followed by the context.
    pub context: Option<Context>,
}

/// 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 = 17;

/// Postcard, matching [`volumes`](crate::provider::endpoints::volumes) and the
/// rest of this family's store.
impl Encode for Frame {
    /// Postcard's own failure. The tag cannot fail.
    type Error = postcard::Error;

    fn encode(&self, out: &mut Writer<'_>) -> Result<(), Self::Error> {
        out.extend_from_slice(&[TAG]);
        postcard::to_io(self, &mut *out)?;
        Ok(())
    }
}

impl Decode<'_> for Frame {
    /// Three ways to fail, and only one of them is postcard's.
    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));
        }
        postcard::from_bytes(rest).map_err(FrameError::Body)
    }
}

/// An image build request 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(postcard::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("image build request frame is empty"),
            FrameError::UnexpectedTag(tag) => {
                write!(f, "expected image build request tag {TAG}, found {tag}")
            }
            FrameError::Body(error) => write!(f, "image build 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,
        }
    }
}
```

and the context by `diverge-sdk-rs/src/provider/endpoints/images/build/client/request/context.rs`:

```rust
//! The directory a build reads from.

use serde::{Deserialize, Serialize};

/// A directory of one of the caller's volumes, as the context and the
/// working directory of a build: the same two fields a
/// [`VolumeMount`](crate::shared::containers::request::VolumeMount)
/// names its host side by, and nothing else.
///
/// # What the build does with it
///
/// Reads it. The Containerfile's `COPY` and `ADD` resolve against it,
/// and so does a `RUN --mount=type=bind` from the context; nothing a
/// build does writes into it, since a Containerfile copies in and not
/// out. So there is no mode here: a build takes the volume's shared
/// hold for its life, as a mount does, and a volume that cannot give
/// one — held exclusively, or a persistent volume held at all — is
/// the build refused.
///
/// # The same access model as a mount
///
/// The volume is one of the caller's listing, named by name, and the
/// path is components into it. A caller cannot escape upward, because
/// there is no component it can write that means "up" — `..` is a
/// name, not an instruction, and a provider descends names and never
/// resolves a path.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub struct Context {
    /// Which volume, by the name a listing gave it.
    pub volume_name: String,
    /// How far into that volume the context is, as path components
    /// relative to it. Empty is the volume itself.
    pub volume_relative_path: Vec<String>,
}
```
