Request
The payload is the tag byte 2 followed by the id of a running container and an authorization as JSON.
The payload of the request is the tag byte followed by the request as JSON, read as Notation provides:
[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 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:
//! 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:
//! 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,
}