use core::{cell::Cell, ops};
#[doc(hidden)] pub trait GetMock<'a, T> {
type Ref: ops::Deref<Target = T> + 'a;
fn get(&'a self) -> Option<Self::Ref>;
}
pub trait SetMock<'a, T> {
type Guard: 'a + Guard<T>;
fn set(&'a self, state: T) -> Self::Guard;
}
pub trait Guard<T> {
fn with<R>(&mut self, action: impl FnOnce(&mut T) -> R) -> R;
fn into_inner(self) -> T;
}
#[doc(hidden)]
pub trait LockMock<'a, T>: SetMock<'a, T> {
type EmptyGuard: 'a;
fn lock(&'a self) -> Self::EmptyGuard;
}
pub trait Wrap<T>: From<T> {
fn into_inner(self) -> T;
fn as_mut(&mut self) -> &mut T;
}
impl<T> Wrap<T> for T {
fn into_inner(self) -> T {
self
}
fn as_mut(&mut self) -> &mut T {
self
}
}
pub trait CheckRealCall {
fn should_call_real(&self) -> bool {
false
}
}
pub trait CallReal {
fn real_switch(&self) -> &RealCallSwitch;
fn call_real<R>(&self, action: impl FnOnce() -> R) -> R {
let switch = <Self as CallReal>::real_switch(self);
switch.0.set(RealCallMode::Always);
let _guard = RealCallGuard { switch };
action()
}
fn call_real_once<R>(&self, action: impl FnOnce() -> R) -> R {
let switch = <Self as CallReal>::real_switch(self);
switch.0.set(RealCallMode::Once);
let _guard = RealCallGuard { switch };
action()
}
}
impl<T: CallReal> CheckRealCall for T {
fn should_call_real(&self) -> bool {
self.real_switch().should_delegate()
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum RealCallMode {
Inactive,
Always,
Once,
}
impl Default for RealCallMode {
fn default() -> Self {
Self::Inactive
}
}
#[derive(Debug, Default)]
pub struct RealCallSwitch(Cell<RealCallMode>);
impl RealCallSwitch {
fn should_delegate(&self) -> bool {
let mode = self.0.get();
if mode == RealCallMode::Once {
self.0.set(RealCallMode::Inactive);
}
mode != RealCallMode::Inactive
}
}
#[derive(Debug)]
struct RealCallGuard<'a> {
switch: &'a RealCallSwitch,
}
impl Drop for RealCallGuard<'_> {
fn drop(&mut self) {
self.switch.0.set(RealCallMode::Inactive);
}
}