snops 0.1.0

The snarkops control plane responsible for managing environments and agents
use snops_common::state::AgentId;

use super::prelude::*;
use crate::schema::nodes::{ExternalNode, Node, NodeFormatHeader};

#[derive(Debug, Clone)]
pub struct PersistNodeFormatHeader {
    pub(crate) node: NodeFormatHeader,
    pub(crate) external_node: DataHeaderOf<ExternalNode>,
}

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum PersistNode {
    Internal(AgentId, Box<Node>),
    External(ExternalNode),
}

impl DataFormat for PersistNodeFormatHeader {
    type Header = u8;
    const LATEST_HEADER: Self::Header = 1;

    fn write_data<W: Write>(&self, writer: &mut W) -> Result<usize, DataWriteError> {
        Ok(write_dataformat(writer, &self.node)? + write_dataformat(writer, &self.external_node)?)
    }

    fn read_data<R: Read>(reader: &mut R, header: &Self::Header) -> Result<Self, DataReadError> {
        if *header != Self::LATEST_HEADER {
            return Err(DataReadError::unsupported(
                "PersistNodeFormatHeader",
                Self::LATEST_HEADER,
                header,
            ));
        }

        let node = read_dataformat(reader)?;
        let external_node = read_dataformat(reader)?;

        Ok(PersistNodeFormatHeader {
            node,
            external_node,
        })
    }
}

impl DataFormat for PersistNode {
    type Header = PersistNodeFormatHeader;
    const LATEST_HEADER: Self::Header = PersistNodeFormatHeader {
        node: Node::LATEST_HEADER,
        external_node: ExternalNode::LATEST_HEADER,
    };

    fn write_data<W: Write>(&self, writer: &mut W) -> Result<usize, DataWriteError> {
        let mut written = 0;
        match self {
            PersistNode::Internal(id, state) => {
                written += writer.write_data(&0u8)?;
                written += writer.write_data(id)?;
                written += writer.write_data(state)?;
            }
            PersistNode::External(n) => {
                written += writer.write_data(&1u8)?;
                written += writer.write_data(n)?;
            }
        }
        Ok(written)
    }

    fn read_data<R: Read>(reader: &mut R, header: &Self::Header) -> Result<Self, DataReadError> {
        match reader.read_data(&())? {
            0u8 => {
                let id = reader.read_data(&())?;
                let state = reader.read_data(&header.node)?;
                Ok(PersistNode::Internal(id, Box::new(state)))
            }
            1u8 => {
                let n = reader.read_data(&header.external_node)?;
                Ok(PersistNode::External(n))
            }
            n => Err(DataReadError::Custom(format!(
                "invalid PersistNode discriminant: {n}"
            ))),
        }
    }
}

#[cfg(test)]
mod tests {
    use std::str::FromStr;

    use snops_common::{
        format::DataFormat,
        node_targets::NodeTargets,
        state::{DocHeightRequest, InternedId},
    };

    use crate::{
        persist::{PersistNode, PersistNodeFormatHeader},
        schema::nodes::{ExternalNode, Node, NodeFormatHeader},
    };

    macro_rules! case {
        ($name:ident, $ty:ty, $a:expr, $b:expr) => {
            #[test]
            fn $name() -> Result<(), Box<dyn std::error::Error>> {
                let mut data = Vec::new();
                let value: $ty = $a;
                value.write_data(&mut data)?;
                assert_eq!(data, $b);

                let mut reader = &data[..];
                let read_value =
                    <$ty>::read_data(&mut reader, &<$ty as DataFormat>::LATEST_HEADER)?;

                let mut rewritten = Vec::new();
                read_value.write_data(&mut rewritten)?;
                assert_eq!(data, rewritten);
                Ok(())
            }
        };
    }

    case!(
        node_header,
        PersistNodeFormatHeader,
        PersistNode::LATEST_HEADER,
        [
            NodeFormatHeader::LATEST_HEADER.to_byte_vec()?,
            Node::LATEST_HEADER.to_byte_vec()?,
            ExternalNode::LATEST_HEADER.to_byte_vec()?,
        ]
        .concat()
    );

    case!(
        node_internal,
        PersistNode,
        PersistNode::Internal(
            InternedId::from_str("id")?,
            Box::new(Node {
                online: true,
                replicas: None,
                key: None,
                height: DocHeightRequest::Top,
                labels: Default::default(),
                agent: None,
                validators: NodeTargets::None,
                peers: NodeTargets::None,
                env: Default::default(),
                binary: None,
            })
        ),
        [
            0u8.to_byte_vec()?,
            InternedId::from_str("id")?.to_byte_vec()?,
            Node {
                online: true,
                replicas: None,
                key: None,
                height: DocHeightRequest::Top,
                labels: Default::default(),
                agent: None,
                validators: NodeTargets::None,
                peers: NodeTargets::None,
                env: Default::default(),
                binary: None,
            }
            .to_byte_vec()?,
        ]
        .concat()
    );

    case!(
        node_external,
        PersistNode,
        PersistNode::External(ExternalNode {
            bft: None,
            node: None,
            rest: None
        }),
        [
            1u8.to_byte_vec()?,
            ExternalNode {
                bft: None,
                node: None,
                rest: None
            }
            .to_byte_vec()?,
        ]
        .concat()
    );
}