use crate::{OrthoError, OrthoResult};
use std::sync::Arc;
pub trait OrthoResultExt<T, E> {
fn into_ortho(self) -> OrthoResult<T>;
}
impl<T, E> OrthoResultExt<T, E> for Result<T, E>
where
E: Into<OrthoError>,
{
fn into_ortho(self) -> OrthoResult<T> {
self.map_err(|e| Arc::new(e.into()))
}
}
pub trait OrthoMergeExt<T> {
fn into_ortho_merge(self) -> OrthoResult<T>;
}
impl<T> OrthoMergeExt<T> for Result<T, figment::Error> {
fn into_ortho_merge(self) -> OrthoResult<T> {
self.map_err(|e| Arc::new(OrthoError::merge(e)))
}
}
#[cfg(feature = "serde_json")]
pub trait OrthoJsonMergeExt<T> {
fn into_ortho_merge_json(self) -> OrthoResult<T>;
}
#[cfg(feature = "serde_json")]
impl<T> OrthoJsonMergeExt<T> for Result<T, serde_json::Error> {
fn into_ortho_merge_json(self) -> OrthoResult<T> {
self.map_err(|e| {
let figment_err = <figment::Error as serde::de::Error>::custom(&e);
Arc::new(OrthoError::merge(figment_err))
})
}
}
pub trait IntoFigmentError {
fn into_figment(self) -> figment::Error;
}
fn clone_figment(shared: &Arc<OrthoError>) -> figment::Error {
match shared.as_ref() {
OrthoError::Merge { source } | OrthoError::Gathering(source) => source.as_ref().clone(),
other => figment::Error::from(other.to_string()),
}
}
impl IntoFigmentError for Arc<OrthoError> {
fn into_figment(self) -> figment::Error {
match Self::try_unwrap(self) {
Ok(err) => err.into(),
Err(shared) => clone_figment(&shared),
}
}
}
impl IntoFigmentError for &Arc<OrthoError> {
fn into_figment(self) -> figment::Error {
clone_figment(self)
}
}
#[expect(
clippy::result_large_err,
reason = "figment::Error is inherently large and this adapter serves only tests."
)]
pub trait ResultIntoFigment<T> {
fn to_figment(self) -> Result<T, figment::Error>;
}
impl<T> ResultIntoFigment<T> for Result<T, Arc<OrthoError>> {
fn to_figment(self) -> Result<T, figment::Error> {
self.map_err(IntoFigmentError::into_figment)
}
}
#[cfg(all(test, feature = "serde_json"))]
mod tests {
use super::*;
use rstest::rstest;
#[rstest]
fn into_ortho_merge_json_produces_merge_error() {
let bad_json: Result<i32, serde_json::Error> = serde_json::from_str("\"not_a_number\"");
let ortho_result = bad_json.into_ortho_merge_json();
let err = ortho_result.expect_err("expected error from invalid JSON");
assert!(
matches!(&*err, OrthoError::Merge { .. }),
"expected Merge error variant, got {err:?}"
);
}
#[rstest]
fn into_ortho_merge_json_preserves_ok_value() {
let ok_result: Result<i32, serde_json::Error> = Ok(42);
let ortho_result = ok_result.into_ortho_merge_json();
assert_eq!(ortho_result.expect("expected Ok"), 42);
}
#[rstest]
fn into_ortho_merge_json_error_message_contains_location() {
let bad_json: Result<i32, serde_json::Error> = serde_json::from_str("\"bad\"");
let ortho_result = bad_json.into_ortho_merge_json();
let err = ortho_result.expect_err("expected error");
let message = err.to_string();
assert!(
message.contains("line") && message.contains("column"),
"error message should contain line and column: {message}"
);
}
}