# Request

> The payload is the tag byte 5 followed by the path of the file as JSON: an array of components from the root of the container.

Canonical: https://protocol.diverge.network/2.3.0/container-proxy-endpoints/filesystem-write/request/
Specification revision: 2.3.0

```text
[5][request JSON …]
```

- **`path`.** An array of strings: the components of the destination
  from the root of the container. Never empty; no component empty,
  `.` or `..`, or containing `/` or NUL.
- **No content.** The request carries no content and no length: the
  content arrives on the channel the server opens.
- **Malformed.** A payload that does not decode as stated is answered
  by a response finish that no response precedes.

The request is defined by `diverge-sdk-rs/src/container_proxy/outside/endpoints/filesystem/write/client/request/frame.rs`:

```rust
//! What a client's request frame carries for a write.

use serde::{Deserialize, Serialize};

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

/// One file, written into the container.
///
/// No length and no mode: the content travels on the channel the
/// proxy opens — see [`write`](super::super) for why it goes that
/// direction — and what the file looks like on disk is the
/// container's business.
#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize, Default)]
pub struct Frame {
    /// The destination, as path components from the container's
    /// root — the same meaning of "path" as everywhere else in this
    /// crate, and the same frame of reference a
    /// [`filetree`](crate::shared::filetree) stream uses. Components
    /// rather than a joined string: a path is a sequence, and joining
    /// it would invent a separator that then has to be escaped out
    /// of names containing it.
    pub path: Vec<String>,
}

/// 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
/// [`container_proxy_endpoints`](crate::container_proxy::outside) 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 = 5;

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)
    }
}

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_err(FrameError::Body)
    }
}

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