Skip to content

Commit 695f90e

Browse files
committed
feat(tx-cache): Pagination types and impl
wip
1 parent 09b2026 commit 695f90e

File tree

2 files changed

+242
-19
lines changed

2 files changed

+242
-19
lines changed

crates/tx-cache/src/client.rs

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
use crate::types::{
2-
TxCacheOrdersResponse, TxCacheSendBundleResponse, TxCacheSendTransactionResponse,
3-
TxCacheTransactionsResponse,
2+
PaginationQuery, TxCacheOrdersResponse, TxCacheSendBundleResponse,
3+
TxCacheSendTransactionResponse, TxCacheTransactionsResponse,
44
};
55
use alloy::consensus::TxEnvelope;
66
use eyre::Error;
@@ -114,6 +114,38 @@ impl TxCache {
114114
.map_err(Into::into)
115115
}
116116

117+
async fn get_inner_with_query<T>(
118+
&self,
119+
join: &'static str,
120+
query: PaginationQuery,
121+
) -> Result<T, Error>
122+
where
123+
T: DeserializeOwned,
124+
{
125+
// Append the path to the URL.
126+
let url = self
127+
.url
128+
.join(join)
129+
.inspect_err(|e| warn!(%e, "Failed to join URL. Not querying transaction cache."))?;
130+
131+
let mut request = self.client.get(url);
132+
133+
if let Some(cursor) = query.cursor() {
134+
request = request.query(&[("cursor", cursor)]);
135+
}
136+
if let Some(limit) = query.limit() {
137+
request = request.query(&[("limit", limit)]);
138+
}
139+
140+
request
141+
.send()
142+
.await
143+
.inspect_err(|e| warn!(%e, "Failed to get object from transaction cache."))?
144+
.json::<T>()
145+
.await
146+
.map_err(Into::into)
147+
}
148+
117149
/// Forwards a raw transaction to the URL.
118150
#[instrument(skip_all)]
119151
pub async fn forward_raw_transaction(
@@ -140,17 +172,27 @@ impl TxCache {
140172

141173
/// Get transactions from the URL.
142174
#[instrument(skip_all)]
143-
pub async fn get_transactions(&self) -> Result<Vec<TxEnvelope>, Error> {
144-
let response: TxCacheTransactionsResponse =
145-
self.get_inner::<TxCacheTransactionsResponse>(TRANSACTIONS).await?;
146-
Ok(response.transactions)
175+
pub async fn get_transactions(
176+
&self,
177+
query: Option<PaginationQuery>,
178+
) -> Result<TxCacheTransactionsResponse, Error> {
179+
if let Some(query) = query {
180+
self.get_inner_with_query::<TxCacheTransactionsResponse>(TRANSACTIONS, query).await
181+
} else {
182+
self.get_inner::<TxCacheTransactionsResponse>(TRANSACTIONS).await
183+
}
147184
}
148185

149186
/// Get signed orders from the URL.
150187
#[instrument(skip_all)]
151-
pub async fn get_orders(&self) -> Result<Vec<SignedOrder>, Error> {
152-
let response: TxCacheOrdersResponse =
153-
self.get_inner::<TxCacheOrdersResponse>(ORDERS).await?;
154-
Ok(response.orders)
188+
pub async fn get_orders(
189+
&self,
190+
query: Option<PaginationQuery>,
191+
) -> Result<TxCacheOrdersResponse, Error> {
192+
if let Some(query) = query {
193+
self.get_inner_with_query::<TxCacheOrdersResponse>(ORDERS, query).await
194+
} else {
195+
self.get_inner::<TxCacheOrdersResponse>(ORDERS).await
196+
}
155197
}
156198
}

crates/tx-cache/src/types.rs

Lines changed: 190 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -90,11 +90,13 @@ impl TxCacheBundleResponse {
9090
pub struct TxCacheBundlesResponse {
9191
/// the list of bundles
9292
pub bundles: Vec<TxCacheBundle>,
93+
/// The pagination info.
94+
pub pagination: PaginationInfo,
9395
}
9496

9597
impl From<Vec<TxCacheBundle>> for TxCacheBundlesResponse {
9698
fn from(bundles: Vec<TxCacheBundle>) -> Self {
97-
Self { bundles }
99+
Self { bundles, pagination: PaginationInfo::empty() }
98100
}
99101
}
100102

@@ -104,23 +106,54 @@ impl From<TxCacheBundlesResponse> for Vec<TxCacheBundle> {
104106
}
105107
}
106108

109+
impl From<(Vec<TxCacheBundle>, PaginationInfo)> for TxCacheBundlesResponse {
110+
fn from((bundles, pagination): (Vec<TxCacheBundle>, PaginationInfo)) -> Self {
111+
Self { bundles, pagination }
112+
}
113+
}
114+
107115
impl TxCacheBundlesResponse {
108116
/// Create a new bundle response from a list of bundles.
109117
pub const fn new(bundles: Vec<TxCacheBundle>) -> Self {
110-
Self { bundles }
118+
Self { bundles, pagination: PaginationInfo::empty() }
111119
}
112120

113121
/// Create a new bundle response from a list of bundles.
114122
#[deprecated = "Use `From::from` instead, `Self::new` in const contexts"]
115123
pub const fn from_bundles(bundles: Vec<TxCacheBundle>) -> Self {
116-
Self::new(bundles)
124+
Self { bundles, pagination: PaginationInfo::empty() }
117125
}
118126

119127
/// Convert the bundle response to a list of [`SignetEthBundle`].
120128
#[deprecated = "Use `this.bundles` instead."]
121129
pub fn into_bundles(self) -> Vec<TxCacheBundle> {
122130
self.bundles
123131
}
132+
133+
/// Check if the response is empty (has no bundles).
134+
pub fn is_empty(&self) -> bool {
135+
self.bundles.is_empty()
136+
}
137+
138+
/// Check if there is a next page in the response.
139+
pub const fn has_next_page(&self) -> bool {
140+
self.pagination.has_next_page()
141+
}
142+
143+
/// Get the cursor for the next page.
144+
pub fn next_cursor(&self) -> Option<&str> {
145+
self.pagination.next_cursor()
146+
}
147+
148+
/// Consume the response and return the next cursor.
149+
pub fn into_next_cursor(self) -> Option<String> {
150+
self.pagination.into_next_cursor()
151+
}
152+
153+
/// Consume the response and return the parts.
154+
pub fn into_parts(self) -> (Vec<TxCacheBundle>, PaginationInfo) {
155+
(self.bundles, self.pagination)
156+
}
124157
}
125158

126159
/// Represents a response to successfully adding or updating a bundle in the transaction cache.
@@ -154,11 +187,13 @@ impl From<TxCacheSendBundleResponse> for uuid::Uuid {
154187
pub struct TxCacheTransactionsResponse {
155188
/// The list of transactions.
156189
pub transactions: Vec<TxEnvelope>,
190+
/// The pagination info.
191+
pub pagination: PaginationInfo,
157192
}
158193

159194
impl From<Vec<TxEnvelope>> for TxCacheTransactionsResponse {
160195
fn from(transactions: Vec<TxEnvelope>) -> Self {
161-
Self { transactions }
196+
Self { transactions, pagination: PaginationInfo::empty() }
162197
}
163198
}
164199

@@ -168,23 +203,54 @@ impl From<TxCacheTransactionsResponse> for Vec<TxEnvelope> {
168203
}
169204
}
170205

206+
impl From<(Vec<TxEnvelope>, PaginationInfo)> for TxCacheTransactionsResponse {
207+
fn from((transactions, pagination): (Vec<TxEnvelope>, PaginationInfo)) -> Self {
208+
Self { transactions, pagination }
209+
}
210+
}
211+
171212
impl TxCacheTransactionsResponse {
172213
/// Instantiate a new transaction response from a list of transactions.
173214
pub const fn new(transactions: Vec<TxEnvelope>) -> Self {
174-
Self { transactions }
215+
Self { transactions, pagination: PaginationInfo::empty() }
175216
}
176217

177218
/// Create a new transaction response from a list of transactions.
178219
#[deprecated = "Use `From::from` instead, or `Self::new` in const contexts"]
179220
pub const fn from_transactions(transactions: Vec<TxEnvelope>) -> Self {
180-
Self::new(transactions)
221+
Self { transactions, pagination: PaginationInfo::empty() }
181222
}
182223

183224
/// Convert the transaction response to a list of [`TxEnvelope`].
184225
#[deprecated = "Use `this.transactions` instead."]
185226
pub fn into_transactions(self) -> Vec<TxEnvelope> {
186227
self.transactions
187228
}
229+
230+
/// Check if the response is empty (has no transactions).
231+
pub fn is_empty(&self) -> bool {
232+
self.transactions.is_empty()
233+
}
234+
235+
/// Check if there is a next page in the response.
236+
pub const fn has_next_page(&self) -> bool {
237+
self.pagination.has_next_page()
238+
}
239+
240+
/// Get the cursor for the next page.
241+
pub fn next_cursor(&self) -> Option<&str> {
242+
self.pagination.next_cursor()
243+
}
244+
245+
/// Consume the response and return the next cursor.
246+
pub fn into_next_cursor(self) -> Option<String> {
247+
self.pagination.into_next_cursor()
248+
}
249+
250+
/// Consume the response and return the parts.
251+
pub fn into_parts(self) -> (Vec<TxEnvelope>, PaginationInfo) {
252+
(self.transactions, self.pagination)
253+
}
188254
}
189255

190256
/// Response from the transaction cache to successfully adding a transaction.
@@ -230,11 +296,13 @@ impl TxCacheSendTransactionResponse {
230296
pub struct TxCacheOrdersResponse {
231297
/// The list of signed orders.
232298
pub orders: Vec<SignedOrder>,
299+
/// The pagination info.
300+
pub pagination: PaginationInfo,
233301
}
234302

235303
impl From<Vec<SignedOrder>> for TxCacheOrdersResponse {
236304
fn from(orders: Vec<SignedOrder>) -> Self {
237-
Self { orders }
305+
Self { orders, pagination: PaginationInfo::empty() }
238306
}
239307
}
240308

@@ -244,21 +312,134 @@ impl From<TxCacheOrdersResponse> for Vec<SignedOrder> {
244312
}
245313
}
246314

315+
impl From<(Vec<SignedOrder>, PaginationInfo)> for TxCacheOrdersResponse {
316+
fn from((orders, pagination): (Vec<SignedOrder>, PaginationInfo)) -> Self {
317+
Self { orders, pagination }
318+
}
319+
}
320+
247321
impl TxCacheOrdersResponse {
248322
/// Create a new order response from a list of orders.
249323
pub const fn new(orders: Vec<SignedOrder>) -> Self {
250-
Self { orders }
324+
Self { orders, pagination: PaginationInfo::empty() }
251325
}
252326

253327
/// Create a new order response from a list of orders.
254328
#[deprecated = "Use `From::from` instead, `Self::new` in const contexts"]
255329
pub const fn from_orders(orders: Vec<SignedOrder>) -> Self {
256-
Self { orders }
330+
Self { orders, pagination: PaginationInfo::empty() }
257331
}
258332

259333
/// Convert the order response to a list of [`SignedOrder`].
260334
#[deprecated = "Use `this.orders` instead."]
261335
pub fn into_orders(self) -> Vec<SignedOrder> {
262336
self.orders
263337
}
338+
339+
/// Check if there is a next page in the response.
340+
pub const fn has_next_page(&self) -> bool {
341+
self.pagination.has_next_page()
342+
}
343+
344+
/// Get the cursor for the next page.
345+
pub fn next_cursor(&self) -> Option<&str> {
346+
self.pagination.next_cursor()
347+
}
348+
349+
/// Consume the response and return the next cursor.
350+
pub fn into_next_cursor(self) -> Option<String> {
351+
self.pagination.into_next_cursor()
352+
}
353+
354+
/// Consume the response and return the parts.
355+
pub fn into_parts(self) -> (Vec<SignedOrder>, PaginationInfo) {
356+
(self.orders, self.pagination)
357+
}
358+
}
359+
360+
/// Represents the pagination information from a transaction cache response.
361+
/// This applies to all GET endpoints that return a list of items.
362+
#[derive(Debug, Clone, Serialize, Deserialize)]
363+
pub struct PaginationInfo {
364+
next_cursor: Option<String>,
365+
has_next_page: bool,
366+
}
367+
368+
impl PaginationInfo {
369+
/// Create a new pagination info.
370+
pub const fn new(next_cursor: Option<String>, has_next_page: bool) -> Self {
371+
Self { next_cursor, has_next_page }
372+
}
373+
374+
/// Create an empty pagination info.
375+
pub const fn empty() -> Self {
376+
Self { next_cursor: None, has_next_page: false }
377+
}
378+
379+
/// Get the next cursor.
380+
pub fn next_cursor(&self) -> Option<&str> {
381+
self.next_cursor.as_deref()
382+
}
383+
384+
/// Convert the pagination info to a next cursor.
385+
pub fn into_next_cursor(self) -> Option<String> {
386+
self.next_cursor
387+
}
388+
389+
/// Check if there is a next page.
390+
pub const fn has_next_page(&self) -> bool {
391+
self.has_next_page
392+
}
393+
}
394+
395+
/// A query for pagination.
396+
#[derive(Clone, Debug, Serialize, Deserialize)]
397+
pub struct PaginationQuery {
398+
/// The cursor to start from.
399+
cursor: Option<String>,
400+
/// The number of items to return.
401+
limit: Option<u32>,
402+
}
403+
404+
impl Default for PaginationQuery {
405+
fn default() -> Self {
406+
Self { cursor: None, limit: None }
407+
}
408+
}
409+
410+
impl PaginationQuery {
411+
/// Creates a new instance of [`PaginationQuery`].
412+
pub const fn new(cursor: Option<String>, limit: Option<u32>) -> Self {
413+
Self { cursor, limit }
414+
}
415+
416+
/// Gets the cursor to start from.
417+
pub fn cursor(&self) -> Option<&str> {
418+
self.cursor.as_deref()
419+
}
420+
421+
/// Consumes the [`PaginationQuery`] and returns the cursor.
422+
pub fn into_cursor(self) -> Option<String> {
423+
self.cursor
424+
}
425+
426+
/// Gets the number of items to return.
427+
pub const fn limit(&self) -> Option<u32> {
428+
self.limit
429+
}
430+
431+
/// Check if the query has a cursor.
432+
pub const fn has_cursor(&self) -> bool {
433+
self.cursor.is_some()
434+
}
435+
436+
/// Check if the query has a limit.
437+
pub const fn has_limit(&self) -> bool {
438+
self.limit.is_some()
439+
}
440+
441+
/// Check if the query is empty (has no cursor and no limit).
442+
pub const fn is_empty(&self) -> bool {
443+
!self.has_cursor() && !self.has_limit()
444+
}
264445
}

0 commit comments

Comments
 (0)