snops 0.1.0

The snarkops control plane responsible for managing environments and agents
use std::path::PathBuf;

use axum::http::StatusCode;
use serde::{ser::SerializeStruct, Serialize, Serializer};
use snops_common::{
    aot_cmds::error::CommandError,
    impl_into_status_code, impl_into_type_str,
    key_source::KeySourceError,
    node_targets::NodeTargetError,
    state::{InternedId, StorageId},
};
use strum_macros::AsRefStr;
use thiserror::Error;
use url::Url;

use super::storage::BinarySourceError;

#[derive(Debug, Error, AsRefStr)]
pub enum StorageError {
    #[error("storage id: `{1}`: {0}")]
    Command(CommandError, StorageId),
    #[error("invalid storage id {0}")]
    InvalidStorageId(String),
    #[error("mkdirs for storage generation id: `{0}`: {1}")]
    GenerateStorage(StorageId, #[source] std::io::Error),
    #[error("remove storage {0:#?}: {1}")]
    RemoveStorage(PathBuf, #[source] std::io::Error),
    #[error("generating genesis id: `{0}`: {1}")]
    FailedToGenGenesis(StorageId, #[source] std::io::Error),
    #[error("fetching genesis block id: `{0}` url: `{1}`: {2}")]
    FailedToFetchGenesis(StorageId, Url, #[source] reqwest::Error),
    #[error("writing genesis block id: `{0}`: {1}")]
    FailedToWriteGenesis(StorageId, #[source] std::io::Error),
    #[error("creating ledger dir id: `{0}`: {1}")]
    FailedToCreateLedgerDir(StorageId, #[source] std::io::Error),
    #[error("taring ledger id: `{0}`: {1}")]
    FailedToTarLedger(StorageId, #[source] std::io::Error),
    #[error("the specified storage ID {0} doesn't exist, and no generation params were specified")]
    NoGenerationParams(StorageId),
    #[error("reading balances {0:#?}: {1}")]
    ReadBalances(PathBuf, #[source] std::io::Error),
    #[error("reading version {0:#?}: {1}")]
    ReadVersion(PathBuf, #[source] std::io::Error),
    #[error("writing version {0:#?}: {1}")]
    WriteVersion(PathBuf, #[source] std::io::Error),
    #[error("writing commmittee {0:#?}: {1}")]
    WriteCommittee(PathBuf, #[source] std::io::Error),
    #[error("parsing balances {0:#?}: {1}")]
    ParseBalances(PathBuf, #[source] serde_json::Error),
    #[error("error loading checkpoints: {0}")]
    CheckpointManager(#[from] snops_checkpoint::errors::ManagerLoadError),
    #[error("binary with id `{0}` does not exist for storage id: {1}")]
    BinaryDoesNotExist(InternedId, StorageId),
    #[error("failed fetching binary with id `{0}` from url `{1}`: {2}")]
    FailedToFetchBinary(InternedId, Url, #[source] reqwest::Error),
    #[error("failed fetching binary with id `{0}` from url `{1}`: status {2}")]
    FailedToFetchBinaryWithStatus(InternedId, Url, StatusCode),
    #[error("failed to create binary file with id `{0}`: {1}")]
    FailedToCreateBinaryFile(InternedId, #[source] std::io::Error),
    #[error("failed to write binary file with id `{0}`: {1}")]
    FailedToWriteBinaryFile(InternedId, #[source] std::io::Error),
    #[error("missing binary file `{0}`: {1}")]
    BinaryFileMissing(InternedId, PathBuf),
    #[error("binary file `{0}` at path `{1}` has size mismatch: expected {2}, found {3}")]
    BinarySizeMismatch(InternedId, PathBuf, u64, u64),
    #[error("binary file `{0}` at path `{1}` has sha256 mismatch expected `{2}`, found `{3}`")]
    BinarySha256Mismatch(InternedId, PathBuf, String, String),
    #[error("binary file `{0}` at path `{1}` has failed check: {2}")]
    BinaryCheckFailed(InternedId, PathBuf, String),
    #[error("failed to check/modify file permissions `{0}`: {1}")]
    PermissionError(PathBuf, std::io::Error),
    #[error("failed to parse binary `{0}`: {1}")]
    BinaryParse(InternedId, BinarySourceError),
}

impl_into_status_code!(StorageError, |value| match value {
    Command(e, _) => e.into(),
    FailedToFetchGenesis(_, _, _) => StatusCode::MISDIRECTED_REQUEST,
    NoGenerationParams(_) => StatusCode::BAD_REQUEST,
    BinaryDoesNotExist(_, _) => StatusCode::NOT_FOUND,
    BinaryFileMissing(_, _) => StatusCode::NOT_FOUND,
    _ => StatusCode::INTERNAL_SERVER_ERROR,
});

impl_into_type_str!(StorageError, |value| match value {
    Command(e, _) => format!("{}.{}", value.as_ref(), e.as_ref()),
    _ => value.as_ref().to_string(),
});

#[derive(Debug, Error, AsRefStr)]
pub enum SchemaError {
    #[error("key source error: {0}")]
    KeySource(#[from] KeySourceError),
    #[error(transparent)]
    NodeTarget(#[from] NodeTargetError),
    #[error(transparent)]
    Storage(#[from] StorageError),
    #[error("query parse error: {0}")]
    QueryParse(String),
}

impl_into_status_code!(SchemaError, |value| match value {
    KeySource(e) => e.into(),
    NodeTarget(e) => e.into(),
    Storage(e) => e.into(),
    QueryParse(_) => StatusCode::BAD_REQUEST,
});

impl_into_type_str!(SchemaError, |value| match value {
    KeySource(e) => format!("{}.{}", value.as_ref(), e.as_ref()),
    Storage(e) => format!("{}.{}", value.as_ref(), String::from(e)),
    QueryParse(e) => format!("{}.{}", value.as_ref(), e),
    _ => value.as_ref().to_string(),
});

impl Serialize for SchemaError {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let mut state = serializer.serialize_struct("Error", 2)?;
        state.serialize_field("type", &String::from(self))?;
        state.serialize_field("error", &self.to_string())?;

        state.end()
    }
}