/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 /requests connection 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,
        }
    }
}