Pumpkin/pumpkin-codecs/src/data_result.rs
Alexander Medvedev 2fcc9bcf4a chore: enforce some more clippy lints
pumpkin contributors will love me :cap:
2026-05-20 22:27:54 +02:00

699 lines
27 KiB
Rust

use crate::lifecycle::Lifecycle;
/// Collects the partial value and message from a `DataResult` if it is an error.
/// Returns an [`Option`] of the provided `DataResult`.
/// - The partial value is stored into `$partial_name`.
/// - If a message is found, it is pushed to `$messages_vec`.
macro_rules! collect_partial_and_message {
($partial_name:ident, $result:ident, $messages_vec:ident) => {
let $partial_name = match $result {
DataResult::Success { result, .. } => Some(result),
DataResult::Error {
message,
partial_result,
..
} => {
$messages_vec.push(message);
partial_result
}
};
};
}
/// A macro to generate a function to apply a function to each result of `n` `DataResult`s.
macro_rules! impl_apply {
(@internal_method $self:ident $f:ident $($result:ident),+) => {
let result_1 = $self;
if !(result_1.is_error() $(|| $result.is_error())+) {
// All n results are successful.
return DataResult::new_success($f(
result_1.into_result().unwrap()
$( , $result.into_result().unwrap() )+
));
}
let mut messages: Vec<String> = vec![];
// Collect any found errors.
collect_partial_and_message!(partial_1, result_1, messages);
$( collect_partial_and_message!($result, $result, messages); )+
return DataResult::new_option_error_with_lifecycle(
messages.join("; "),
match (partial_1, $($result, )+) {
(Some(result_1), $(Some($result), )+) => Some($f(result_1 $(, $result )+)),
_ => None,
},
Lifecycle::Experimental,
);
};
($name:ident, $n:literal, $($ty:ident, $result:ident),+) => {
#[doc = concat!("Applies a function to each result of ", stringify!($n), " `DataResult`s of different types.")]
///
/// - If any of the given results is a non-result, the returned result will also be a non-result.
/// - Any errors found in error results (non-result or partial result) will be added to the returned result.
/// - If all results are at least partial, `f` is called, which should return the final item to be wrapped in the returned result.
///
/// The returned result is a *success* **if and only if** all provided results are successes as well.
pub fn $name<$($ty,)+ T>(
self,
f: impl FnOnce(R $(,$ty)+) -> T
$(, $result: DataResult<$ty>)+
) -> DataResult<T> {
impl_apply!(@internal_method self f $($result),+);
}
};
(expect $name:ident, $n:literal, $($ty:ident, $result:ident),+) => {
#[doc = concat!("Applies a function to each result of ", stringify!($n), " `DataResult`s of different types.")]
///
/// - If any of the given results is a non-result, the returned result will also be a non-result.
/// - Any errors found in error results (non-result or partial result) will be added to the returned result.
/// - If all results are at least partial, `f` is called, which should return the final item to be wrapped in the returned result.
///
/// The returned result is a *success* **if and only if** all provided results are successes as well.
#[expect(clippy::too_many_arguments)]
pub fn $name<$($ty,)+ T>(
self,
f: impl FnOnce(R $(,$ty)+) -> T
$(, $result: DataResult<$ty>)+
) -> DataResult<T> {
impl_apply!(@internal_method self f $($result),+);
}
};
}
/// A result that can either represent a successful result, or a
/// *partial* or non-result with an error.
///
/// `R` is the type of result stored.
#[derive(Clone, Debug)]
#[must_use]
pub enum DataResult<R> {
/// Contains a complete result and has no error.
Success { result: R, lifecycle: Lifecycle },
/// Contains no or a partial result and has an error.
/// The error is a *format string*.
Error {
partial_result: Option<R>,
lifecycle: Lifecycle,
message: String,
},
}
impl<R> DataResult<R> {
/// Returns this `DataResult`'s lifecycle.
pub const fn lifecycle(&self) -> Lifecycle {
match self {
Self::Success { lifecycle, .. } | Self::Error { lifecycle, .. } => *lifecycle,
}
}
/// Sets this `DataResult`'s lifecycle and returns a new result.
pub fn with_lifecycle(self, new_lifecycle: Lifecycle) -> Self {
match self {
Self::Success { result, .. } => Self::Success {
result,
lifecycle: new_lifecycle,
},
Self::Error {
partial_result,
message,
..
} => Self::Error {
partial_result,
message,
lifecycle: new_lifecycle,
},
}
}
/// Adds another `Lifecycle` to this `DataResult`'s lifecycle and returns the new result.
pub fn add_lifecycle(self, added_lifecycle: Lifecycle) -> Self {
let new_lifecycle = self.lifecycle().add(added_lifecycle);
self.with_lifecycle(new_lifecycle)
}
/// Returns a *successful* `DataResult` with an experimental lifecycle.
#[inline]
pub const fn new_success(result: R) -> Self {
Self::new_success_with_lifecycle(result, Lifecycle::Experimental)
}
/// Returns a *successful* `DataResult` with a given lifecycle.
#[inline]
pub const fn new_success_with_lifecycle(result: R, lifecycle: Lifecycle) -> Self {
Self::Success { result, lifecycle }
}
/// Returns an *errored* `DataResult` with no result and an experimental lifecycle.
#[inline]
pub fn new_error(error: impl Into<String>) -> Self {
Self::new_error_with_lifecycle(error.into(), Lifecycle::Experimental)
}
/// Returns an *errored* `DataResult` with a partial result and an experimental lifecycle.
#[inline]
pub fn new_partial_error(error: impl Into<String>, partial_result: R) -> Self {
Self::new_partial_error_with_lifecycle(
error.into(),
partial_result,
Lifecycle::Experimental,
)
}
/// Returns an *errored* `DataResult` with no result and a given lifecycle.
#[inline]
pub fn new_error_with_lifecycle<T>(
message: impl Into<String>,
lifecycle: Lifecycle,
) -> DataResult<T> {
DataResult::Error {
partial_result: None,
lifecycle,
message: message.into(),
}
}
/// Returns an *errored* `DataResult` with a partial result and a given lifecycle.
#[inline]
pub fn new_partial_error_with_lifecycle(
message: impl Into<String>,
partial_result: R,
lifecycle: Lifecycle,
) -> Self {
Self::Error {
partial_result: Some(partial_result),
lifecycle,
message: message.into(),
}
}
/// Returns an *errored* `DataResult` with result [`Option<R>`] and a given lifecycle.
#[inline]
const fn new_option_error_with_lifecycle(
message: String,
partial_result: Option<R>,
lifecycle: Lifecycle,
) -> Self {
Self::Error {
partial_result,
lifecycle,
message,
}
}
/// Tries to get a complete result from this `DataResult`. If no such result exists, this returns [`None`] (even for partial results).
///
/// To allow partial results, use [`DataResult::into_result_or_partial`].
#[inline]
pub fn into_result(self) -> Option<R> {
if let Self::Success { result, .. } = self {
Some(result)
} else {
None
}
}
/// Tries to get a complete or partial result. If no such result exists, this returns [`None`].
pub fn into_result_or_partial(self) -> Option<R> {
match self {
Self::Success { result, .. } => Some(result),
Self::Error { partial_result, .. } => partial_result,
}
}
/// Tries to get a complete or partial result as a reference. If no such result exists, this returns [`None`].
pub const fn result_or_partial_as_ref(&self) -> Option<&R> {
match self {
Self::Success { result, .. } => Some(result),
Self::Error { partial_result, .. } => partial_result.as_ref(),
}
}
/// Tries to get a complete result from this `DataResult`. If no such result exists, this function panics.
pub fn unwrap(self) -> R {
self.expect("No complete result found for DataResult")
}
/// Tries to get a complete or partial result from this `DataResult`. If no such result exists, this function panics.
pub fn unwrap_or_partial(self) -> R {
self.expect_or_partial("No complete or partial result found for DataResult")
}
/// Tries to get a complete result from this `DataResult`. If no such result exists, this function panics with a custom message.
pub fn expect(self, message: &str) -> R {
self.into_result().unwrap_or_else(|| panic!("{}", message))
}
/// Tries to get a complete or partial result from this `DataResult`. If no such result exists, this function panics with a custom message.
pub fn expect_or_partial(self, message: &str) -> R {
self.into_result_or_partial()
.unwrap_or_else(|| panic!("{}", message))
}
/// Returns whether this `DataResult` has a complete or partial result.
pub const fn has_result_or_partial(&self) -> bool {
!matches!(
self,
Self::Error {
partial_result: None,
..
}
)
}
/// Appends two messages to form a bigger one.
/// This is useful for stacking message for data results with more than 1 error.
#[must_use]
pub fn append_messages(first: &str, second: &str) -> String {
format!("{first}; {second}")
}
/// Maps a `DataResult` of a type `R` to a `DataResult` of a type `T` by applying a function, leaving non-results untouched.
///
/// `f` is applied to complete results and partial ones. For partial results, `f` is applied to their partial value.
pub fn map<T>(self, op: impl FnOnce(R) -> T) -> DataResult<T> {
match self {
Self::Success { result, lifecycle } => {
DataResult::new_success_with_lifecycle(op(result), lifecycle)
}
Self::Error {
partial_result,
lifecycle,
message,
} => DataResult::new_option_error_with_lifecycle(
message,
partial_result.map(op),
lifecycle,
),
}
}
/// Maps a `DataResult` of a type `R` to a type `T`.
/// - If there is a complete result, `f` (the result function) is called with that result.
/// - Otherwise, if there is an error, `default` (the error function) is called with the error as the parameter.
pub fn map_or_else<T>(self, default: impl FnOnce(Self) -> T, f: impl Fn(R) -> T) -> T {
match self {
Self::Success { result, .. } => f(result),
Self::Error { .. } => default(self),
}
}
/// Chains a `DataResult` with another function taking a `DataResult`.
/// - If there is a complete or partial result, `f` is called with that result, and the value returned by `f` is returned.
/// For a partial result, new messages are propagated via concatenation.
/// - Otherwise, if there is an error with no result, this propagates this error `DataResult`.
///
/// In other words, `f` will process the complete or partial result of this `DataResult` (if any), appending errors if necessary.
///
/// The name of this function is equivalent to `and_then`.
pub fn flat_map<T>(self, f: impl FnOnce(R) -> DataResult<T>) -> DataResult<T> {
match self {
Self::Success { result, lifecycle } => {
// Add this DataResult's lifecycle to the new DataResult.
f(result).add_lifecycle(lifecycle)
}
Self::Error {
partial_result,
lifecycle,
message,
} => {
if let Some(result) = partial_result {
// Try mapping the internal partial value.
let second_result = f(result);
let new_lifecycle = second_result.lifecycle().add(lifecycle);
match second_result {
DataResult::Success { result, .. } => {
DataResult::new_partial_error_with_lifecycle(
message,
result,
new_lifecycle,
)
}
DataResult::Error {
partial_result,
message: second_message,
..
} => DataResult::new_option_error_with_lifecycle(
Self::append_messages(&message, &second_message),
partial_result,
new_lifecycle,
),
}
} else {
// Return this same Error.
DataResult::Error {
partial_result: None,
lifecycle,
message,
}
}
}
}
}
/// Applies a function wrapped in a `DataResult` to the value wrapped in this `DataResult`.
pub fn apply<T>(self, function_result: DataResult<impl FnOnce(R) -> T>) -> DataResult<T> {
let lifecycle = self.lifecycle().add(function_result.lifecycle());
match (self, function_result) {
(Self::Success { result, .. }, DataResult::Success { result: f, .. }) => {
DataResult::new_success_with_lifecycle(f(result), lifecycle)
}
(
Self::Success { result, .. },
DataResult::Error {
partial_result,
message: func_message,
..
},
) => DataResult::new_option_error_with_lifecycle(
func_message,
partial_result.map(|f| f(result)),
lifecycle,
),
(
Self::Error {
partial_result,
message,
..
},
DataResult::Success { result: f, .. },
) => DataResult::new_option_error_with_lifecycle(
message,
partial_result.map(f),
lifecycle,
),
(
Self::Error {
partial_result,
message,
..
},
DataResult::Error {
partial_result: partial_func_result,
message: func_message,
..
},
) => DataResult::new_option_error_with_lifecycle(
Self::append_messages(&message, &func_message),
partial_result.and_then(|r| partial_func_result.map(|f| f(r))),
lifecycle,
),
}
}
/// Similar to [`Self::apply_2`], but this also marks the returned `DataResult` as [`Lifecycle::Stable`].
pub fn apply_2_and_make_stable<R2, T>(
self,
f: impl FnOnce(R, R2) -> T,
second_result: DataResult<R2>,
) -> DataResult<T> {
self.apply_2(f, second_result)
.with_lifecycle(Lifecycle::Stable)
}
impl_apply!(apply_2, 2, R2, second_result);
impl_apply!(apply_3, 3, R2, result_2, R3, result_3);
impl_apply!(apply_4, 4, R2, result_2, R3, result_3, R4, result_4);
impl_apply!(
apply_5, 5, R2, result_2, R3, result_3, R4, result_4, R5, result_5
);
impl_apply!(
apply_6, 6, R2, result_2, R3, result_3, R4, result_4, R5, result_5, R6, result_6
);
impl_apply!(expect apply_7, 7, R2, result_2, R3, result_3, R4, result_4, R5, result_5, R6, result_6, R7, result_7);
impl_apply!(expect apply_8, 8, R2, result_2, R3, result_3, R4, result_4, R5, result_5, R6, result_6, R7, result_7, R8, result_8);
impl_apply!(expect apply_9, 9, R2, result_2, R3, result_3, R4, result_4, R5, result_5, R6, result_6, R7, result_7, R8, result_8, R9, result_9);
impl_apply!(expect apply_10, 10, R2, result_2, R3, result_3, R4, result_4, R5, result_5, R6, result_6, R7, result_7, R8, result_8, R9, result_9, R10, result_10);
impl_apply!(expect apply_11, 11, R2, result_2, R3, result_3, R4, result_4, R5, result_5, R6, result_6, R7, result_7, R8, result_8, R9, result_9, R10, result_10, R11, result_11);
impl_apply!(expect apply_12, 12, R2, result_2, R3, result_3, R4, result_4, R5, result_5, R6, result_6, R7, result_7, R8, result_8, R9, result_9, R10, result_10, R11, result_11, R12, result_12);
impl_apply!(expect apply_13, 13, R2, result_2, R3, result_3, R4, result_4, R5, result_5, R6, result_6, R7, result_7, R8, result_8, R9, result_9, R10, result_10, R11, result_11, R12, result_12, R13, result_13);
impl_apply!(expect apply_14, 14, R2, result_2, R3, result_3, R4, result_4, R5, result_5, R6, result_6, R7, result_7, R8, result_8, R9, result_9, R10, result_10, R11, result_11, R12, result_12, R13, result_13, R14, result_14);
impl_apply!(expect apply_15, 15, R2, result_2, R3, result_3, R4, result_4, R5, result_5, R6, result_6, R7, result_7, R8, result_8, R9, result_9, R10, result_10, R11, result_11, R12, result_12, R13, result_13, R14, result_14, R15, result_15);
impl_apply!(expect apply_16, 16, R2, result_2, R3, result_3, R4, result_4, R5, result_5, R6, result_6, R7, result_7, R8, result_8, R9, result_9, R10, result_10, R11, result_11, R12, result_12, R13, result_13, R14, result_14, R15, result_15, R16, result_16);
/// Applies a function to `DataResult` errors, leaving successes untouched.
/// This can be used to provide additional context to an error.
pub fn map_error(self, f: impl FnOnce(String) -> String) -> Self {
match self {
Self::Success { .. } => self,
Self::Error {
message,
lifecycle,
partial_result,
} => Self::new_option_error_with_lifecycle(f(message), partial_result, lifecycle),
}
}
/// Promotes a `DataResult` containing a partial result to a success `DataResult`, providing
/// the error message to a function `f` (consumer function) and removing it from the new `DataResult`.
/// `DataResult`s with no result or a complete result are left untouched.
pub fn promote_partial(self, f: impl FnOnce(String)) -> Self {
match self {
Self::Success { .. } => self,
Self::Error {
message,
lifecycle,
partial_result,
} => {
f(message.clone());
partial_result.map_or_else(
|| Self::new_error_with_lifecycle(message, lifecycle),
|result| Self::new_success_with_lifecycle(result, lifecycle),
)
}
}
}
/// Returns a `DataResult` with a new partial value (always partial), leaving `DataResult`s with a complete result untouched.
pub fn with_partial(self, partial_value: R) -> Self {
match self {
Self::Success { .. } => self,
Self::Error {
message, lifecycle, ..
} => Self::new_partial_error_with_lifecycle(message, partial_value, lifecycle),
}
}
/// Returns a `DataResult` with a new result/partial result, depending on the type of `DataResult` this is.
/// - For a complete result, this returns another `DataResult` whose complete result is `value`.
/// - For a partial result, this returns another `DataResult` whose partial result is `value`.
/// - For a non-result, this returns itself.
pub fn with_complete_or_partial<T>(self, value: T) -> DataResult<T> {
match self {
Self::Success { lifecycle, .. } => {
DataResult::new_success_with_lifecycle(value, lifecycle)
}
Self::Error {
message,
lifecycle,
partial_result: Some(_),
} => DataResult::new_partial_error_with_lifecycle(message, value, lifecycle),
Self::Error {
message, lifecycle, ..
} => Self::new_error_with_lifecycle(message, lifecycle),
}
}
/// Returns whether this `DataResult` was a success.
pub const fn is_success(&self) -> bool {
matches!(self, &Self::Success { .. })
}
/// Returns whether this `DataResult` was an error (including partial result errors).
pub const fn is_error(&self) -> bool {
!self.is_success()
}
/// Add a message of another `DataResult` (`other_result`) to this `DataResult`.
///
/// This is useful for *unit tuple* `DataResult`s used simply for final results of complex objects.
/// - If `other_result` is a complete result, nothing happens.
/// - If both results are partial, the returned result is also partial. Otherwise, it is a non-result.
/// - Messages found in any `DataResult` error are concatenated and used in the returned result.
///
/// This always returns a *stable* result.
pub fn add_message<T>(self, other_result: &DataResult<T>) -> Self {
match (self, other_result) {
// Both results are successful.
(Self::Success { result: r, .. }, DataResult::Success { .. }) => {
Self::new_success_with_lifecycle(r, Lifecycle::Stable)
}
// Both results are errors.
(
Self::Error {
partial_result: p1,
message: m1,
..
},
DataResult::Error {
partial_result: p2,
message: m2,
..
},
) => Self::new_option_error_with_lifecycle(
Self::append_messages(&m1, m2),
if p1.is_some() && p2.is_some() {
p1
} else {
None
},
Lifecycle::Stable,
),
// Exactly one of both results is an error.
(
Self::Error {
message: m1,
partial_result,
..
},
_,
) => Self::new_option_error_with_lifecycle(m1, partial_result, Lifecycle::Stable),
(
Self::Success { result, .. },
DataResult::Error {
message: m2,
partial_result,
..
},
) => Self::new_option_error_with_lifecycle(
m2.clone(),
partial_result.is_some().then_some(result),
Lifecycle::Stable,
),
}
}
/// Tries to add errors from the given `result` and adds them to `self`
/// if `self` is not already an error result.
///
/// The [`Lifecycle`] of the returned `DataResult` is the addition of both results.
pub fn with_errors_from<T>(self, result: &DataResult<T>) -> Self {
let current_lifecycle = self.lifecycle();
match (self, result) {
(s @ Self::Error { .. }, _) | (s, DataResult::Success { .. }) => s,
(
Self::Success { result: val, .. },
DataResult::Error {
message,
lifecycle: other_lifecycle,
..
},
) => Self::new_partial_error_with_lifecycle(
message.clone(),
val,
current_lifecycle.add(*other_lifecycle),
),
}
}
/// Returns the message of this `DataResult`, if any. Only error results have messages.
pub fn get_message(self) -> Option<String> {
match self {
Self::Success { .. } => None,
Self::Error { message, .. } => Some(message),
}
}
}
// Assertion functions
/// Asserts that the `$left` `DataResult` is a complete result (success) whose stored result is `$right`.
#[macro_export]
macro_rules! assert_success {
($left:expr, $right:expr $(,)?) => {{
let result = $left;
assert!(
result.is_success(),
"Expected a `DataResult` success, got: {:?}",
result
);
assert_eq!(
result.unwrap(),
$right,
"`DataResult` was successful but the value doesn't match"
);
}};
}
/// Asserts that encoding the left expression will lead to a complete result (success) whose stored result is `$right`.
#[macro_export]
macro_rules! assert_encode_success {
($left:expr, $ops:expr, $right:expr $(,)?) => {{
let result = $crate::codec::Encode::encode_start(&$left, &$ops);
assert!(
result.is_success(),
"Expected a `DataResult` success, got: {:?}",
result
);
assert_eq!(
result.unwrap(),
$right,
"`DataResult` was successful but the value doesn't match"
);
}};
}
/// Asserts that decoding the left expression will lead to a `DataResult` whose provided method returns `true`.
#[macro_export]
macro_rules! assert_decode {
($ty:ty, $input:expr, $ops:expr, $func:ident $(,)?) => {{
let result = <$ty as $crate::codec::Decode>::parse($input, &$ops);
assert!(
result.$func(),
concat!(
"Expected a `DataResult` that returns `true` for ",
stringify!($func),
", got: {:?}"
),
result
);
}};
}
impl<T> Default for DataResult<T> {
fn default() -> Self {
Self::new_error("Default DataResult")
}
}
/// A type conversion from one type to this type that may fail, resulting in a [`DataResult`].
///
/// Always prefer using [`FlatTryFrom`] over [`FlatTryInto`] for implementing the conversion,
/// as an implementation of [`FlatTryInto`] will automatically work as well.
pub trait FlatTryFrom<T>: Sized {
/// Performs the conversion.
fn flat_try_from(value: T) -> DataResult<Self>;
}
impl<T> FlatTryFrom<T> for T {
fn flat_try_from(value: T) -> DataResult<Self> {
DataResult::new_success(value)
}
}
impl<T, U> FlatTryInto<U> for T
where
U: FlatTryFrom<T>,
{
#[inline]
/// Calls `U::flat_try_from()`, which performs the conversion.
fn flat_try_into(self) -> DataResult<U> {
U::flat_try_from(self)
}
}
/// A type conversion from this type to another that may fail, resulting in a [`DataResult`].
///
/// Always prefer using [`FlatTryFrom`] over [`FlatTryInto`] for implementing the conversion,
/// as an implementation of [`FlatTryInto`] will automatically work as well.
pub trait FlatTryInto<T>: Sized {
/// Performs the conversion.
fn flat_try_into(self) -> DataResult<T>;
}