# Request

> The payload is the tag byte 2 followed by the id of a running container and an authorization as JSON.

Canonical: https://provider.diverge.network/2.3.0/endpoints/containers-tools-connect/request/
Specification revision: 2.3.0

The payload of the request is the tag byte followed by the request as
JSON, read as [Notation](/2.3.0/endpoints/#notation) provides:

```text
[2][connect JSON …]
```

- **`id`.** The id a run's response carried. The server resolves it
  among the containers it is running; it means nothing outside the
  server that minted it.
- **`authorization`.** A string the server relays to the runner
  verbatim and does not read. It may be empty.
- **Malformed.** A payload with no byte, a payload whose first byte
  is not `2`, and a payload whose bytes after the tag do not parse 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-provider-sdk/src/endpoints/containers/tools/connect/client/request/frame.rs`:

```rust
//! What a client's request frame carries for a tool container connection.

use crate::decode::Decode;
use crate::encode::{Encode, Writer};
use crate::shared::containers::request::Connect;

/// Ask to join a tool container somebody else is running.
///
/// A [`Connect`] and nothing else: the id and the authorization. See
/// it for what happens to the authorization, which is nothing here.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Frame(
    /// Which container, and what the runner is offered.
    pub Connect,
);

/// 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::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::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 = 2;

/// JSON, like every other structured request.
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.0)
    }
}

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(Frame).map_err(FrameError::Body)
    }
}

/// A tool container connection 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(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("tools connect request frame is empty")
            }
            FrameError::UnexpectedTag(tag) => {
                write!(f, "expected tools connect request tag {TAG}, found {tag}")
            }
            FrameError::Body(error) => {
                write!(f, "tools connect 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 by `diverge-provider-sdk/src/shared/containers/request/connect.rs`:

```rust
//! Joining a container somebody else runs.

use serde::{Deserialize, Serialize};

/// Ask to join a container somebody else is running.
///
/// # What happens to the authorization
///
/// Nothing, here. The provider relays it to whoever holds the
/// container's run scope, as an
/// [`Authorize`](crate::shared::containers::authorize::request::Authorize),
/// and the answer to that is whether this scope opens.
///
/// Which is why the credential is opaque. A provider that had to
/// understand it would have to know what makes one connector
/// acceptable and another not, and it does not — the runner does, and
/// the runner is who reads it.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub struct Connect {
    /// The container to join.
    ///
    /// An [`Id`](crate::shared::containers::response::Id) from a run.
    /// It means nothing to a connector that was not given it, and
    /// nothing outside the provider that minted it.
    pub id: String,
    /// Whatever the runner needs in order to say yes.
    ///
    /// Opaque, and relayed verbatim. A shared secret, a signed token,
    /// a name — this layer does not know and does not look, so nothing
    /// here constrains what a runner chooses to require.
    ///
    /// Text, for the same reason an
    /// [`Auth`](crate::frame::auth::Auth) credential is: what this
    /// carries in practice already is a string, and bytes made a
    /// caller pick an encoding for something that never needed one.
    ///
    /// May be empty, which is a connector offering nothing. Whether
    /// that is ever enough is the runner's to decide.
    pub authorization: String,
}
```
