use std::fmt::Display;
use anyhow::format_err;
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug)]
pub enum RiverResult<T, E: ToString> {
Ok(T),
Err {
message: String,
code: E,
},
}
impl<T, E: ToString> RiverResult<T, E> {
#[must_use]
pub const fn is_ok(&self) -> bool {
matches!(self, Self::Ok(..))
}
#[must_use]
pub const fn is_err(&self) -> bool {
matches!(self, Self::Err { .. })
}
}
impl<T, E: TryFrom<String, Error = E2> + ToString, E2: Display> TryFrom<RiverResultInternal<T>>
for RiverResult<T, E>
{
type Error = anyhow::Error;
fn try_from(result: RiverResultInternal<T>) -> Result<Self, Self::Error> {
if result.ok {
if let Some(inner) = result.inner {
Ok(RiverResult::Ok(inner))
} else {
Err(format_err!("Expected inner to be Some when ok is true"))
}
} else {
if let Some(code) = result.code {
if let Some(message) = result.message {
return Ok(RiverResult::Err {
code: code.try_into().map_err(|e| format_err!("{e}"))?,
message,
});
}
}
Err(format_err!(
"Expected code and reason to be Some when ok is false"
))
}
}
}
#[derive(Serialize, Deserialize, Clone, Debug)]
#[serde(rename_all = "camelCase")]
pub struct RiverResultInternal<T> {
ok: bool,
message: Option<String>,
code: Option<String>,
#[serde(flatten)]
inner: Option<T>,
}
impl<T, E: ToString> From<RiverResult<T, E>> for RiverResultInternal<T> {
fn from(result: RiverResult<T, E>) -> Self {
match result {
RiverResult::Ok(val) => RiverResultInternal {
ok: true,
message: None,
code: None,
inner: Some(val),
},
RiverResult::Err {
message: reason,
code,
} => RiverResultInternal {
ok: false,
message: Some(reason),
code: Some(code.to_string()),
inner: None,
},
}
}
}