Skip to content

Commit d14d250

Browse files
Edward AlmondEdward Almond
authored andcommitted
Implement S3 pagination for ListObjectsV2
- Decode continuation token (base64 encoded last key) - Skip keys up to and including the token's key - Return is_truncated=true when max_keys is hit - Generate next_continuation_token as base64(last_key)
1 parent 14af110 commit d14d250

1 file changed

Lines changed: 29 additions & 3 deletions

File tree

ruststack-s3/src/storage/ephemeral.rs

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
33
use super::traits::*;
44
use async_trait::async_trait;
5+
use base64::{engine::general_purpose::STANDARD as BASE64, Engine};
56
use bytes::Bytes;
67
use chrono::Utc;
78
use dashmap::DashMap;
@@ -195,7 +196,7 @@ impl ObjectStorage for EphemeralStorage {
195196
bucket: &str,
196197
prefix: Option<&str>,
197198
delimiter: Option<&str>,
198-
_continuation_token: Option<&str>,
199+
continuation_token: Option<&str>,
199200
max_keys: i32,
200201
) -> Result<ListObjectsResult, StorageError> {
201202
let bucket_ref = self
@@ -206,15 +207,32 @@ impl ObjectStorage for EphemeralStorage {
206207
let prefix = prefix.unwrap_or("");
207208
let max_keys = max_keys as usize;
208209

210+
let mut skip_key: Option<String> = None;
211+
if let Some(token) = continuation_token {
212+
if let Ok(decoded) = BASE64.decode(token) {
213+
if let Ok(key) = String::from_utf8(decoded) {
214+
skip_key = Some(key);
215+
}
216+
}
217+
}
218+
209219
let mut objects = Vec::new();
210220
let mut common_prefixes = std::collections::HashSet::new();
221+
let mut last_key: Option<String> = None;
222+
let mut has_more = false;
211223

212224
for entry in bucket_ref.objects.iter() {
213225
let key = entry.key();
214226
if !key.starts_with(prefix) {
215227
continue;
216228
}
217229

230+
if let Some(ref skip) = skip_key {
231+
if key <= skip {
232+
continue;
233+
}
234+
}
235+
218236
let suffix = &key[prefix.len()..];
219237

220238
// Handle delimiter
@@ -228,9 +246,11 @@ impl ObjectStorage for EphemeralStorage {
228246
}
229247

230248
if objects.len() >= max_keys {
249+
has_more = true;
231250
break;
232251
}
233252

253+
last_key = Some(key.clone());
234254
objects.push(ObjectSummary {
235255
key: key.clone(),
236256
etag: entry.etag.clone(),
@@ -243,11 +263,17 @@ impl ObjectStorage for EphemeralStorage {
243263
// Sort by key
244264
objects.sort_by(|a, b| a.key.cmp(&b.key));
245265

266+
let next_token = if has_more {
267+
last_key.map(|k| BASE64.encode(k.as_bytes()))
268+
} else {
269+
None
270+
};
271+
246272
Ok(ListObjectsResult {
247273
objects,
248274
common_prefixes: common_prefixes.into_iter().collect(),
249-
is_truncated: false, // TODO: Implement pagination
250-
next_continuation_token: None,
275+
is_truncated: has_more,
276+
next_continuation_token: next_token,
251277
})
252278
}
253279

0 commit comments

Comments
 (0)