You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@opendal.apache.org by er...@apache.org on 2026/08/15 07:17:12 UTC
(opendal) 07/07: fix(services/onedrive): honor list start after
This is an automated email from the ASF dual-hosted git repository.
erickguan pushed a commit to branch fix/onedrive-behavior-followups
in repository https://gitbox.apache.org/repos/asf/opendal.git
commit 50cd01b267cf616c64d03b07685d37ebdc7e45e8
Author: Erick Guan <29...@users.noreply.github.com>
AuthorDate: Sat Aug 15 14:02:36 2026 +0800
fix(services/onedrive): honor list start after
---
core/services/onedrive/src/backend.rs | 4 +-
core/services/onedrive/src/core.rs | 87 +++++++++++++++++++++++++++++++++++
core/services/onedrive/src/lister.rs | 16 ++++++-
3 files changed, 104 insertions(+), 3 deletions(-)
diff --git a/core/services/onedrive/src/backend.rs b/core/services/onedrive/src/backend.rs
index f8c29f182..62098a5ca 100644
--- a/core/services/onedrive/src/backend.rs
+++ b/core/services/onedrive/src/backend.rs
@@ -149,6 +149,7 @@ impl Builder for OnedriveBuilder {
list: true,
list_with_limit: true,
+ list_with_start_after: true,
list_with_versions: true,
shared: true,
@@ -362,12 +363,13 @@ mod tests {
use super::OnedriveBuilder;
#[test]
- fn capability_does_not_advertise_versioned_stat() {
+ fn capabilities_match_supported_version_listing() {
let backend = OnedriveBuilder::default()
.access_token("test-token")
.build()
.unwrap();
assert!(!backend.capability().stat_with_version);
+ assert!(backend.capability().list_with_start_after);
}
}
diff --git a/core/services/onedrive/src/core.rs b/core/services/onedrive/src/core.rs
index dde05b848..a1349ff05 100644
--- a/core/services/onedrive/src/core.rs
+++ b/core/services/onedrive/src/core.rs
@@ -1002,6 +1002,44 @@ mod tests {
assert_eq!(entries[2].metadata().version(), Some("1.0"));
}
+ #[tokio::test]
+ async fn list_with_versions_respects_start_after() {
+ let core = test_core("/");
+ let ctx = OperationContext::new()
+ .with_http_transport(HttpTransporter::new(StartAfterMockHttpTransport));
+ let lister = OneDriveLister::new(
+ "dir/".to_string(),
+ core,
+ ctx,
+ Capability {
+ list_with_start_after: true,
+ list_with_versions: true,
+ ..Default::default()
+ },
+ &OpList::new()
+ .with_versions(true)
+ .with_start_after("dir/file-2"),
+ );
+ let mut lister = oio::PageLister::new(lister);
+
+ let mut paths = Vec::new();
+ while let Some(entry) = lister.next().await.unwrap() {
+ paths.push(entry.path().to_string());
+ }
+
+ assert_eq!(
+ paths,
+ [
+ "dir/file-3",
+ "dir/file-3",
+ "dir/file-4",
+ "dir/file-4",
+ "dir/file-5",
+ "dir/file-5",
+ ]
+ );
+ }
+
#[derive(Clone)]
struct VersionMockHttpTransport;
@@ -1034,6 +1072,55 @@ mod tests {
.unwrap())
}
}
+
+ #[derive(Clone)]
+ struct StartAfterMockHttpTransport;
+
+ impl HttpTransport for StartAfterMockHttpTransport {
+ async fn fetch(&self, req: Request<Buffer>) -> Result<Response<HttpBody>> {
+ let url = req.uri().to_string();
+ let root_url = OneDriveCore::DRIVE_ROOT_URL;
+
+ let (status, body) = if url
+ == format!("{root_url}:/dir:/children?{GENERAL_SELECT_PARAM}")
+ {
+ let items = (0..6)
+ .map(|index| {
+ format!(
+ r#"{{"id":"{index}","name":"file-{index}","lastModifiedDateTime":"2026-01-01T00:00:00Z","eTag":"tag-{index}","size":5,"parentReference":{{"path":"/drive/root:/dir","driveId":"d","id":"p"}},"file":{{"mimeType":"text/plain"}}}}"#
+ )
+ })
+ .collect::<Vec<_>>()
+ .join(",");
+ (StatusCode::OK, format!(r#"{{"value":[{items}]}}"#))
+ } else if url == format!("{root_url}:/dir") {
+ (
+ StatusCode::OK,
+ r#"{"id":"dir","name":"dir","lastModifiedDateTime":"2026-01-01T00:00:00Z","eTag":"dir-tag","size":0,"parentReference":{"path":"/drive/root:","driveId":"d","id":"p"},"folder":{"childCount":6}}"#.to_string(),
+ )
+ } else if url.starts_with(&format!("{root_url}:/dir/file-"))
+ && url.ends_with(&format!(":/versions?{VERSION_SELECT_PARAM}"))
+ {
+ (StatusCode::OK, VERSION_RESPONSE.to_string())
+ } else {
+ (
+ StatusCode::NOT_FOUND,
+ r#"{"error":{"code":"itemNotFound","message":"Item not found"}}"#.to_string(),
+ )
+ };
+
+ let data = Bytes::from(body);
+ let size = data.len() as u64;
+ Ok(Response::builder()
+ .status(status)
+ .header(header::CONTENT_LENGTH, size)
+ .body(HttpBody::new(
+ stream::iter(vec![Ok(Buffer::from(data))]),
+ Some(size),
+ ))
+ .unwrap())
+ }
+ }
}
mod error {
diff --git a/core/services/onedrive/src/lister.rs b/core/services/onedrive/src/lister.rs
index b001d72eb..ab7bb540b 100644
--- a/core/services/onedrive/src/lister.rs
+++ b/core/services/onedrive/src/lister.rs
@@ -54,6 +54,12 @@ impl OneDriveLister {
op: args.clone(),
}
}
+
+ fn is_after_start(&self, path: &str) -> bool {
+ self.op
+ .start_after()
+ .is_none_or(|start_after| path > start_after)
+ }
}
impl oio::PageList for OneDriveLister {
@@ -100,8 +106,10 @@ impl oio::PageList for OneDriveLister {
// skip `list_with_versions` intentionally because a folder doesn't have versions
- let entry = oio::Entry::new(&path, meta);
- ctx.entries.push_back(entry);
+ if self.is_after_start(&path) {
+ let entry = oio::Entry::new(&path, meta);
+ ctx.entries.push_back(entry);
+ }
}
if let Some(next_link) = decoded_response.next_link {
@@ -129,6 +137,10 @@ impl oio::PageList for OneDriveLister {
normalized_path.push('/');
}
+ if !self.is_after_start(&normalized_path) {
+ continue;
+ }
+
let mut meta = Metadata::new(entry_mode)
.with_etag(drive_item.e_tag)
.with_content_length(drive_item.size.max(0) as u64);