use clap::ValueEnum;
use core::fmt;
use serde::Serialize;
use std::process::{ExitCode, Termination};
use thiserror::Error;
use crate::results::{
BoundaryVersionResult, ComparisonStatement, FilterTestResult, FlatVersionsList, GenerateResult,
OrderedVersionMap, SelectResult, ValidateResult, VersionExplanation, VersionMutationResult,
};
#[derive(Error, Debug)]
pub(crate) enum ApplicationError {
#[error("Invalid input (expected {expected:?}, got {found:?}")]
InvalidArgument { expected: String, found: String },
#[error("Failed to prepare output in this format {err:?}")]
OutputFormatError { err: String },
#[error("Failed a requirement {err:?}")]
FailedRequirementError { err: String },
}
#[derive(ValueEnum, Clone, Debug)]
pub(crate) enum OutputFormat {
Text,
Yaml,
Json,
}
pub(crate) enum ExitOutcome {
Normal(SubcommandResult),
AlwaysSuccessful(SubcommandResult),
}
impl ExitOutcome {
pub(crate) const fn new(output: SubcommandResult, hard_success: bool) -> Self {
if hard_success {
Self::AlwaysSuccessful(output)
} else {
Self::Normal(output)
}
}
}
impl Termination for ExitOutcome {
fn report(self) -> ExitCode {
match self {
Self::Normal(result) => result.report(),
Self::AlwaysSuccessful(_result) => ExitCode::SUCCESS,
}
}
}
macro_rules! subcommand_result {
(
enum $name:ident {
$($variant:ident($ty:ty)),* $(,)?
}
) => {
#[derive(Serialize)]
#[serde(untagged)]
pub(crate) enum $name {
$($variant($ty),)*
}
$(impl From<$ty> for $name {
fn from(value: $ty) -> Self {
Self::$variant(value)
}
})*
impl fmt::Display for $name {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
$(Self::$variant(inner) => write!(f, "{inner}"),)*
}
}
}
};
}
subcommand_result! {
enum SubcommandResult {
ComparisonStatement(ComparisonStatement),
OrderedVersionMap(OrderedVersionMap),
VersionExplanation(VersionExplanation),
FlatVersionsList(FlatVersionsList),
GenerateResult(GenerateResult),
FilterTestResult(FilterTestResult),
ValidateResult(ValidateResult),
VersionMutation(VersionMutationResult),
SelectResult(SelectResult),
BoundaryVersionResult(BoundaryVersionResult),
}
}
impl Termination for SubcommandResult {
fn report(self) -> ExitCode {
match self {
Self::ComparisonStatement(s) => s.report(),
Self::FilterTestResult(s) => s.report(),
Self::ValidateResult(s) => s.report(),
Self::SelectResult(s) => s.report(),
Self::OrderedVersionMap(_)
| Self::VersionExplanation(_)
| Self::FlatVersionsList(_)
| Self::GenerateResult(_)
| Self::VersionMutation(_)
| Self::BoundaryVersionResult(_) => ExitCode::SUCCESS,
}
}
}
pub(crate) fn emit(
result: &SubcommandResult,
format: OutputFormat,
) -> Result<(), ApplicationError> {
match format {
OutputFormat::Text => print!("{result}"),
OutputFormat::Yaml => {
println!("---");
let yaml = serde_yaml::to_string(result)
.map_err(|e| ApplicationError::OutputFormatError { err: e.to_string() })?;
print!("{yaml}");
}
OutputFormat::Json => {
let json = serde_json::to_string(result)
.map_err(|e| ApplicationError::OutputFormatError { err: e.to_string() })?;
println!("{json}");
}
}
Ok(())
}