mirror of
https://github.com/tribufu/rust-gamedig
synced 2026-05-06 15:27:28 +00:00
* protocols: Add more control over gathering additional information Adds GatherToggle which allows choosing the behaviour for how the query handles fetching additional information. The choices are: - DontGather - Don't attempt to fetch information - AttemptGather - Try to fetch the information but ignore errors - Required - Try to fetch information and fail if it errors A handy macro was also added to utils to dispatch additional queries based on a GatherToggle value. * Add/Update badge * protocols: Improve GatherToggle enum names Co-Authored-By: Cain <75994858+cainthebest@users.noreply.github.com> Co-Authored-By: CosminPerRam <cosmin.p@live.com> * Add/Update badge --------- Co-authored-by: GitHub Action <action@github.com> Co-authored-by: Cain <75994858+cainthebest@users.noreply.github.com> Co-authored-by: CosminPerRam <cosmin.p@live.com>
208 lines
6.3 KiB
Rust
208 lines
6.3 KiB
Rust
use crate::GDErrorKind::{PacketOverflow, PacketReceive, PacketSend, PacketUnderflow};
|
|
use crate::GDResult;
|
|
use std::cmp::Ordering;
|
|
|
|
pub fn error_by_expected_size(expected: usize, size: usize) -> GDResult<()> {
|
|
match size.cmp(&expected) {
|
|
Ordering::Greater => Err(PacketOverflow.into()),
|
|
Ordering::Less => Err(PacketUnderflow.into()),
|
|
Ordering::Equal => Ok(()),
|
|
}
|
|
}
|
|
|
|
pub const fn u8_lower_upper(n: u8) -> (u8, u8) { (n & 15, n >> 4) }
|
|
|
|
/// Run a closure `retry_count+1` times while it returns [PacketReceive] or
|
|
/// [PacketSend] errors, returning the first success, other Error, or after
|
|
/// `retry_count+1` tries the last [PacketReceive] or [PacketSend] error.
|
|
pub fn retry_on_timeout<T>(mut retry_count: usize, mut fetch: impl FnMut() -> GDResult<T>) -> GDResult<T> {
|
|
let mut last_err = PacketReceive.context("Retry count was 0");
|
|
retry_count += 1;
|
|
while retry_count > 0 {
|
|
last_err = match fetch() {
|
|
Ok(r) => return Ok(r),
|
|
Err(e) if e.kind == PacketReceive || e.kind == PacketSend => e,
|
|
Err(e) => return Err(e),
|
|
};
|
|
retry_count -= 1;
|
|
}
|
|
Err(last_err)
|
|
}
|
|
|
|
/// Run gather_fn based on the value of gather_toggle.
|
|
///
|
|
/// # Parameters
|
|
/// - `gather_toggle` should be an expression resolving to a
|
|
/// [crate::protocols::types::GatherToggle].
|
|
/// - `gather_fn` should be an expression that returns a [crate::GDResult].
|
|
///
|
|
/// # States
|
|
/// - [DontGather](crate::protocols::types::GatherToggle::DontGather) - Don't
|
|
/// run gather function, returns None.
|
|
/// - [AttemptGather](crate::protocols::types::GatherToggle::AttemptGather) -
|
|
/// Runs the gather function, if it returns an error return None, else return
|
|
/// Some.
|
|
/// - [Required](crate::protocols::types::GatherToggle::Required) - Runs the
|
|
/// gather function, if it returns an error propagate it using the `?`
|
|
/// operator, else return Some.
|
|
///
|
|
/// # Examples
|
|
///
|
|
/// ```ignore,Doctests cannot access private items
|
|
/// use gamedig::protocols::types::GatherToggle;
|
|
/// use gamedig::utils::maybe_gather;
|
|
///
|
|
/// let query_fn = || { Err("Query error") };
|
|
///
|
|
/// // query_fn() is not called
|
|
/// let response = maybe_gather!(GatherToggle::DontGather, query_fn());
|
|
/// assert!(response.is_none());
|
|
///
|
|
/// // query_fn() is called but Err is converted to None
|
|
/// let response = maybe_gather!(GatherToggle::AttemptGather, query_fn());
|
|
/// assert!(response.is_none());
|
|
///
|
|
/// // query_fn() is called and Err is propagated.
|
|
/// let response = maybe_gather!(GatherToggle::Required, query_fn());
|
|
/// unreachable!();
|
|
/// ```
|
|
macro_rules! maybe_gather {
|
|
($gather_toggle: expr, $gather_fn: expr) => {
|
|
match $gather_toggle {
|
|
crate::protocols::types::GatherToggle::Skip => None,
|
|
crate::protocols::types::GatherToggle::Try => $gather_fn.ok(),
|
|
crate::protocols::types::GatherToggle::Enforce => Some($gather_fn?),
|
|
}
|
|
};
|
|
}
|
|
|
|
pub(crate) use maybe_gather;
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::retry_on_timeout;
|
|
use crate::{
|
|
protocols::types::GatherToggle,
|
|
GDError,
|
|
GDErrorKind::{self, PacketBad, PacketReceive, PacketSend},
|
|
GDResult,
|
|
};
|
|
|
|
#[test]
|
|
fn u8_lower_upper() {
|
|
assert_eq!(super::u8_lower_upper(171), (11, 10));
|
|
}
|
|
|
|
#[test]
|
|
fn error_by_expected_size() {
|
|
assert!(super::error_by_expected_size(69, 69).is_ok());
|
|
assert!(super::error_by_expected_size(69, 68).is_err());
|
|
assert!(super::error_by_expected_size(69, 70).is_err());
|
|
}
|
|
|
|
#[test]
|
|
fn retry_success_on_first() {
|
|
let r = retry_on_timeout(0, || Ok(()));
|
|
assert!(r.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn retry_no_success() {
|
|
let r: GDResult<()> = retry_on_timeout(100, || Err(PacketSend.context("test")));
|
|
assert!(r.is_err());
|
|
assert_eq!(r.unwrap_err().kind, PacketSend);
|
|
}
|
|
|
|
#[test]
|
|
fn retry_success_on_third() {
|
|
let mut i = 0u8;
|
|
let r = retry_on_timeout(2, || {
|
|
i += 1;
|
|
if i < 3 {
|
|
Err(PacketReceive.context("test"))
|
|
} else {
|
|
Ok(())
|
|
}
|
|
});
|
|
assert!(r.is_ok());
|
|
}
|
|
|
|
#[test]
|
|
fn retry_success_on_third_but_less_retries() {
|
|
let mut i = 0u8;
|
|
let r = retry_on_timeout(1, || {
|
|
i += 1;
|
|
if i < 3 {
|
|
Err(PacketReceive.context("test"))
|
|
} else {
|
|
Ok(())
|
|
}
|
|
});
|
|
assert!(r.is_err());
|
|
assert_eq!(r.unwrap_err().kind, PacketReceive);
|
|
}
|
|
|
|
#[test]
|
|
fn retry_with_non_timeout_error() {
|
|
let mut i = 0u8;
|
|
let r = retry_on_timeout(50, || {
|
|
i += 1;
|
|
match i {
|
|
1 => Err(PacketSend.context("test")),
|
|
2 => Err(PacketBad.context("test")),
|
|
_ => Ok(()),
|
|
}
|
|
});
|
|
assert!(r.is_err());
|
|
assert_eq!(r.unwrap_err().kind, PacketBad);
|
|
}
|
|
|
|
fn gather_success(n: i32) -> GDResult<i32> { Ok(n) }
|
|
|
|
fn gather_fail(err: &'static str) -> GDResult<i32> { Err(GDErrorKind::PacketSend.context(err)) }
|
|
|
|
#[test]
|
|
fn gather_success_dont_gather() -> GDResult<()> {
|
|
let result = maybe_gather!(GatherToggle::Skip, gather_success(5));
|
|
assert!(result.is_none());
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn gather_success_attempt_gather() -> GDResult<()> {
|
|
let result = maybe_gather!(GatherToggle::Try, gather_success(10));
|
|
assert_eq!(result, Some(10));
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn gather_success_required() -> GDResult<()> {
|
|
let result = maybe_gather!(GatherToggle::Enforce, gather_success(15));
|
|
assert_eq!(result, Some(15));
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn gather_fail_dont_gather() -> GDResult<()> {
|
|
let result = maybe_gather!(GatherToggle::Skip, gather_fail("dont"));
|
|
assert!(result.is_none());
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn gather_fail_attempt_gather() -> GDResult<()> {
|
|
let result = maybe_gather!(GatherToggle::Try, gather_fail("attempt"));
|
|
assert!(result.is_none());
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn gather_fail_required() {
|
|
let inner = || {
|
|
let result = maybe_gather!(GatherToggle::Enforce, gather_fail("required"));
|
|
assert_eq!(result, Some(10));
|
|
Ok::<(), GDError>(())
|
|
};
|
|
assert!(inner().is_err());
|
|
}
|
|
}
|