1use std::{
2 fmt::{self, Formatter},
3 num::NonZeroUsize,
4};
5
6use crate::matcher::InvocationMatcher;
7
8pub struct Stub<'a, I, O> {
9 matcher: Box<dyn InvocationMatcher<I> + Send>,
10 answer: Answer<'a, I, O>,
11}
12
13pub enum Answer<'a, I, O> {
14 Exhausted,
15 Once(Box<dyn FnOnce(I) -> O + Send + 'a>),
16 Many {
17 stub: Box<dyn FnMut(I) -> O + Send + 'a>,
18 times: Times,
19 },
20}
21
22#[derive(Debug, Clone, Copy)]
23pub enum Times {
24 Always,
25 Times(NonZeroUsize),
26}
27
28#[derive(Debug)]
29pub enum Error {
30 Exhausted,
31 NotMatched(String),
32}
33
34impl std::error::Error for Error {}
35
36impl fmt::Display for Error {
37 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
38 match self {
39 Error::Exhausted => f.write_str("stub was exhausted"),
40 Error::NotMatched(error) => f.write_str(error),
41 }
42 }
43}
44
45impl Times {
46 pub fn decrement(self) -> Option<Self> {
47 match self {
48 Times::Always => Some(self),
49 Times::Times(n) => NonZeroUsize::new(n.get() - 1).map(Times::Times),
50 }
51 }
52}
53
54impl<'a, I, O> Stub<'a, I, O> {
55 pub fn new(
56 stub: Answer<'a, I, O>,
57 matcher: impl InvocationMatcher<I> + Send + 'static,
58 ) -> Self {
59 Stub {
60 matcher: Box::new(matcher),
61 answer: stub,
62 }
63 }
64
65 pub fn call(&mut self, input: I) -> Result<O, (I, Error)> {
66 if let Err(e) = self.matcher.matches(&input) {
68 return Err((input, Error::NotMatched(e)));
69 }
70
71 self.answer.call(input)
72 }
73}
74
75impl<I, O> Answer<'_, I, O> {
76 fn call(&mut self, input: I) -> Result<O, (I, Error)> {
77 if let Answer::Many { stub, times } = self {
79 if let Some(decremented) = times.decrement() {
80 *times = decremented;
81 return Ok(stub(input));
82 }
83 }
84
85 match std::mem::replace(self, Answer::Exhausted) {
87 Answer::Exhausted => Err((input, Error::Exhausted)),
88 Answer::Once(stub) => Ok(stub(input)),
89 Answer::Many { mut stub, .. } => Ok(stub(input)),
90 }
91 }
92}
93
94impl<I, O> fmt::Debug for Stub<'_, I, O> {
95 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
96 f.debug_struct("Stub")
97 .field(
100 "answer",
101 match &self.answer {
102 Answer::Exhausted => &"Exhausted",
103 Answer::Once(_) => &"Once",
104 Answer::Many { .. } => &"Many",
105 },
106 )
107 .finish()
108 }
109}