/command/{channel}
Kind 10: a command the proxy asks the caller to run, its bytes opaque; the server sends one message per item the command produces, an error as the last message when there is one, and closes the connection; the proxy repeats no command.
The ask is kind 10 on /requests:
[10][command …]
The command is bytes running to the end of the message. Neither the
proxy nor the server reads them. The server answers on
/command/{channel} by sending one message per item the command
produces, and closing the connection. Each message is the frame
defined below: a first byte of 0 is followed by the bytes of the
item; a first byte of 1 is followed by an error as JSON, and such a
message is the last the server sends.
- The end of the command. The server sends each item as the command produces it. The close, or the error, states that no further item follows. A path that the proxy or the server closes cleanly with no message before the close states that the ask was not served.
- No repetition. The proxy does not ask a command again. A
command whose path ended abruptly, and a command whose
/requestsconnection ended before the server opened its path, have an outcome the proxy cannot know, and the proxy reports such a command inside the container as failed.
The ask is defined by diverge-provider-sdk/src/shared/containers/command/request/request.rs:
//! The command, as the container asks it.
use std::convert::Infallible;
use crate::decode::Decode;
use crate::encode::{Encode, Writer};
/// A diverge command: bytes in the CLI's own vocabulary, which this
/// layer never reads. The whole payload after whatever tag or kind
/// names the ask.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct Request<'a>(
/// The command, verbatim.
pub &'a [u8],
);
impl Encode for Request<'_> {
/// [`Infallible`]: bytes copied.
type Error = Infallible;
fn encode(&self, out: &mut Writer<'_>) -> Result<(), Infallible> {
out.extend_from_slice(self.0);
Ok(())
}
}
impl<'a> Decode<'a> for Request<'a> {
/// [`Infallible`]: the bytes are the command.
type Error = Infallible;
fn decode(bytes: &'a [u8]) -> Result<Self, Infallible> {
Ok(Request(bytes))
}
}
The messages are defined by diverge-provider-sdk/src/shared/containers/command/response/frame.rs:
//! One item the command produced, or the news that it will not
//! produce another.
use super::FrameError;
use crate::decode::Decode as _;
use crate::encode::{Encode, Writer};
use crate::shared::error::Error;
/// One item, or the end of them.
///
/// A message leads with one byte saying which — `0` for
/// [`Item`](Self::Item), `1` for [`Error`](Self::Error) — and the
/// rest is that variant's own bytes.
///
/// # An error is not an item
///
/// And the tag is what keeps them apart. Without it a command that
/// failed would have to say so IN an item, in whatever shape the CLI
/// used for that, and a container that did not know the shape could
/// not find out. [`Error`](Self::Error) is the caller saying the
/// command did not finish. What arrived before it is what the
/// command produced; nothing follows it, because the finish comes
/// after.
///
/// # The item is opaque, and the tag does not change that
///
/// The tag says whether there is an item, not what is in one.
#[derive(Debug, Clone, PartialEq)]
pub enum Frame<'a> {
/// One item, borrowed from the message it arrived in. Tag `0`.
Item(&'a [u8]),
/// The command did not finish. Tag `1`.
///
/// See [`shared::error::Error`](crate::shared::error::Error) for
/// why it says so little.
Error(Error),
}
/// Tag for [`Frame::Item`].
const ITEM: u8 = 0;
/// Tag for [`Frame::Error`].
const ERROR: u8 = 1;
impl Encode for Frame<'_> {
/// The ordinary JSON failure, from the only variant that has one.
/// An item is bytes copied.
type Error = serde_json::Error;
// 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<(), serde_json::Error> {
match self {
Frame::Item(item) => {
out.extend_from_slice(&[ITEM]);
out.extend_from_slice(item);
Ok(())
}
Frame::Error(error) => {
out.extend_from_slice(&[ERROR]);
error.encode(out)
}
}
}
}
impl<'a> Frame<'a> {
/// Decode one message. An item borrows from `bytes`.
pub fn decode(bytes: &'a [u8]) -> Result<Self, FrameError> {
let (tag, rest) = bytes.split_first().ok_or(FrameError::Empty)?;
match *tag {
ITEM => Ok(Frame::Item(rest)),
ERROR => Error::decode(rest)
.map(super::Frame::Error)
.map_err(FrameError::Error),
tag => Err(FrameError::UnknownTag(tag)),
}
}
}
The ways in which a message fails to decode are defined by
diverge-provider-sdk/src/shared/containers/command/response/error.rs:
//! Why a command answer could not be decoded.
use std::error;
use std::fmt;
/// A command response that could not be read.
#[derive(Debug)]
pub enum FrameError {
/// No bytes at all, so not even a tag.
///
/// Distinct from an empty item, which is a tag byte followed by
/// nothing and is a command that produced something with no bytes
/// in it.
Empty,
/// A tag that is neither of this response's two.
UnknownTag(u8),
/// The error did not parse.
Error(serde_json::Error),
}
impl fmt::Display for FrameError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
FrameError::Empty => {
f.write_str("command response is empty")
}
FrameError::UnknownTag(tag) => {
write!(f, "unknown command response tag {tag}")
}
FrameError::Error(error) => {
write!(f, "command error did not parse: {error}")
}
}
}
}
impl error::Error for FrameError {
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
match self {
FrameError::Error(error) => Some(error),
FrameError::Empty | FrameError::UnknownTag(_) => None,
}
}
}