Skip to main content

cloud_sdk/pagination/
driver.rs

1use core::fmt;
2
3use super::PaginationError;
4
5/// Provider-neutral page strategy driven without transport or allocation.
6pub trait PageStrategy {
7    /// Exact request-position token for the next page.
8    type Request: Copy;
9    /// Decoded response observation, optionally borrowing provider state.
10    type Observation<'observation>;
11    /// Validated accepted page boundary.
12    type Boundary;
13
14    /// Returns the next request token or [`PaginationError::Complete`].
15    fn next_request(&self) -> Result<Self::Request, PaginationError>;
16
17    /// Transactionally validates and accepts one response.
18    fn observe<'observation>(
19        &mut self,
20        observation: Self::Observation<'observation>,
21    ) -> Result<Self::Boundary, PaginationError>;
22}
23
24/// Caller decision independent from provider pagination state.
25#[derive(Clone, Copy, Debug, Eq, PartialEq)]
26pub enum PagerControl {
27    /// Continue traversal within the strategy's hard budgets.
28    Continue,
29    /// Cancel before another response is accepted.
30    Cancel,
31}
32
33/// Next caller action from the pure pager driver.
34#[derive(Clone, Copy, Debug, Eq, PartialEq)]
35pub enum PagerStep<R> {
36    /// Send one request for this validated position.
37    Request(R),
38    /// The strategy has no continuation.
39    Complete,
40    /// Caller cancellation made the driver terminal.
41    Cancelled,
42}
43
44/// Pager sequencing or strategy failure.
45#[derive(Clone, Copy, Debug, Eq, PartialEq)]
46pub enum PagerDriverError {
47    /// A request is already awaiting one response.
48    ResponsePending,
49    /// A response was supplied without one admitted request.
50    UnexpectedObservation,
51    /// The driver already completed or was cancelled.
52    Terminal,
53    /// The underlying transactional strategy rejected the response.
54    Strategy(PaginationError),
55}
56
57impl fmt::Display for PagerDriverError {
58    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
59        formatter.write_str(match self {
60            Self::ResponsePending => "a pagination response is still pending",
61            Self::UnexpectedObservation => "pagination observation has no admitted request",
62            Self::Terminal => "pagination driver already reached a terminal state",
63            Self::Strategy(_) => "pagination strategy rejected the response",
64        })
65    }
66}
67
68impl core::error::Error for PagerDriverError {}
69
70/// Single-owner request/response sequencer for one pagination strategy.
71///
72/// ```compile_fail
73/// use cloud_sdk::pagination::{NumberedPagination, PagerDriver};
74/// fn duplicate(driver: PagerDriver<NumberedPagination>) {
75///     let _copy = driver.clone();
76/// }
77/// ```
78pub struct PagerDriver<S> {
79    strategy: S,
80    response_pending: bool,
81    terminal: bool,
82}
83
84impl<S> PagerDriver<S>
85where
86    S: PageStrategy,
87{
88    /// Wraps one fresh strategy without executing a request.
89    #[must_use]
90    pub const fn new(strategy: S) -> Self {
91        Self {
92            strategy,
93            response_pending: false,
94            terminal: false,
95        }
96    }
97
98    /// Returns read-only access to strategy progress and limits.
99    #[must_use]
100    pub const fn strategy(&self) -> &S {
101        &self.strategy
102    }
103
104    /// Reports whether no response can be accepted.
105    #[must_use]
106    pub const fn is_terminal(&self) -> bool {
107        self.terminal
108    }
109
110    /// Admits one next request, completes, or cancels.
111    pub fn next_request(
112        &mut self,
113        control: PagerControl,
114    ) -> Result<PagerStep<S::Request>, PagerDriverError> {
115        if self.terminal {
116            return Err(PagerDriverError::Terminal);
117        }
118        if control == PagerControl::Cancel {
119            self.terminal = true;
120            self.response_pending = false;
121            return Ok(PagerStep::Cancelled);
122        }
123        if self.response_pending {
124            return Err(PagerDriverError::ResponsePending);
125        }
126        match self.strategy.next_request() {
127            Ok(request) => {
128                self.response_pending = true;
129                Ok(PagerStep::Request(request))
130            }
131            Err(PaginationError::Complete) => {
132                self.terminal = true;
133                Ok(PagerStep::Complete)
134            }
135            Err(error) => Err(PagerDriverError::Strategy(error)),
136        }
137    }
138
139    /// Accepts exactly one decoded response for the admitted request.
140    pub fn observe<'observation>(
141        &mut self,
142        observation: S::Observation<'observation>,
143    ) -> Result<S::Boundary, PagerDriverError> {
144        if self.terminal {
145            return Err(PagerDriverError::Terminal);
146        }
147        if !self.response_pending {
148            return Err(PagerDriverError::UnexpectedObservation);
149        }
150        let boundary = self
151            .strategy
152            .observe(observation)
153            .map_err(PagerDriverError::Strategy)?;
154        self.response_pending = false;
155        Ok(boundary)
156    }
157}
158
159impl<S> fmt::Debug for PagerDriver<S>
160where
161    S: PageStrategy + fmt::Debug,
162{
163    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
164        formatter
165            .debug_struct("PagerDriver")
166            .field("strategy", &self.strategy)
167            .field("response_pending", &self.response_pending)
168            .field("terminal", &self.terminal)
169            .finish()
170    }
171}