This is an automated email from the ASF dual-hosted git repository.

erickguan pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal.git


The following commit(s) were added to refs/heads/main by this push:
     new b6a0a7a9c fix(services/onedrive): build correct children URL when 
listing root (#7720)
b6a0a7a9c is described below

commit b6a0a7a9c60d050c8ba22a532fb51007483effae
Author: tonghuaroot (童话) <[email protected]>
AuthorDate: Sun Jun 21 13:00:12 2026 +0800

    fix(services/onedrive): build correct children URL when listing root (#7720)
    
    * fix(services/onedrive): build correct children URL when listing root
    
    * docs(services/onedrive): tighten root list-request comment
---
 core/Cargo.lock                      |   1 +
 core/services/onedrive/Cargo.toml    |   1 +
 core/services/onedrive/src/core.rs   | 167 +++++++++++++++++++++++++++++++++++
 core/services/onedrive/src/lister.rs |  25 ++----
 4 files changed, 176 insertions(+), 18 deletions(-)

diff --git a/core/Cargo.lock b/core/Cargo.lock
index b5b4ed5b6..457874deb 100644
--- a/core/Cargo.lock
+++ b/core/Cargo.lock
@@ -7324,6 +7324,7 @@ name = "opendal-service-onedrive"
 version = "0.57.0"
 dependencies = [
  "bytes",
+ "futures",
  "http 1.4.2",
  "log",
  "mea",
diff --git a/core/services/onedrive/Cargo.toml 
b/core/services/onedrive/Cargo.toml
index 79024e42d..e3bf9f06d 100644
--- a/core/services/onedrive/Cargo.toml
+++ b/core/services/onedrive/Cargo.toml
@@ -41,4 +41,5 @@ serde_json = { workspace = true }
 tokio = { workspace = true, features = ["time"] }
 
 [dev-dependencies]
+futures = { workspace = true }
 tokio = { workspace = true, features = ["macros", "rt-multi-thread"] }
diff --git a/core/services/onedrive/src/core.rs 
b/core/services/onedrive/src/core.rs
index 7920c4414..ee2cebba1 100644
--- a/core/services/onedrive/src/core.rs
+++ b/core/services/onedrive/src/core.rs
@@ -269,6 +269,42 @@ impl OneDriveCore {
         Ok(decoded_response.value)
     }
 
+    pub(crate) fn onedrive_list_request(
+        &self,
+        path: &str,
+        limit: Option<usize>,
+    ) -> Result<Request<Buffer>> {
+        let item_url = self.onedrive_item_url(path, true);
+        // Root is addressed as `root/children`, not the path form 
`root:/children`.
+        let mut url = if item_url == Self::DRIVE_ROOT_URL {
+            format!("{item_url}/children?{GENERAL_SELECT_PARAM}")
+        } else {
+            format!("{item_url}:/children?{GENERAL_SELECT_PARAM}")
+        };
+        if let Some(limit) = limit {
+            url += &format!("&$top={limit}");
+        }
+
+        Request::get(&url)
+            .extension(Operation::List)
+            .extension(ServiceOperation("ListChildren"))
+            .body(Buffer::new())
+            .map_err(new_request_build_error)
+    }
+
+    pub(crate) async fn onedrive_list(
+        &self,
+        ctx: &OperationContext,
+        path: &str,
+        limit: Option<usize>,
+    ) -> Result<Response<Buffer>> {
+        let mut request = self.onedrive_list_request(path, limit)?;
+
+        self.sign(ctx, &mut request).await?;
+
+        ctx.http_transport().send(request).await
+    }
+
     pub(crate) async fn onedrive_get_next_list_page(
         &self,
         ctx: &OperationContext,
@@ -750,6 +786,137 @@ impl OneDriveSigner {
     }
 }
 
+#[cfg(test)]
+mod tests {
+    use bytes::Bytes;
+    use futures::stream;
+    use http::StatusCode;
+    use opendal_core::raw::oio::List;
+
+    use super::super::lister::OneDriveLister;
+    use super::*;
+
+    const ROOT_STAT_RESPONSE: &str = 
r#"{"id":"0","name":"root","lastModifiedDateTime":"2026-01-01T00:00:00Z","eTag":"aTag","size":0,"parentReference":{"path":"","driveId":"d","id":"p"},"folder":{"childCount":1}}"#;
+    const ROOT_CHILDREN_RESPONSE: &str = 
r#"{"value":[{"id":"1","name":"test.txt","lastModifiedDateTime":"2026-01-01T00:00:00Z","eTag":"aTag","size":5,"parentReference":{"path":"/drive/root:","driveId":"d","id":"p"},"file":{"mimeType":"text/plain"}}]}"#;
+
+    #[derive(Clone)]
+    struct MockHttpTransport;
+
+    impl HttpTransport for MockHttpTransport {
+        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}/children?{GENERAL_SELECT_PARAM}") {
+                (StatusCode::OK, ROOT_CHILDREN_RESPONSE)
+            } else if url == root_url {
+                (StatusCode::OK, ROOT_STAT_RESPONSE)
+            } else {
+                (
+                    StatusCode::NOT_FOUND,
+                    r#"{"error":{"code":"itemNotFound","message":"Item not 
found"}}"#,
+                )
+            };
+
+            let data = Bytes::from_static(body.as_bytes());
+            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())
+        }
+    }
+
+    fn test_ctx() -> OperationContext {
+        
OperationContext::new().with_http_transport(HttpTransporter::new(MockHttpTransport))
+    }
+
+    fn test_core(root: &str) -> Arc<OneDriveCore> {
+        let info = ServiceInfo::new("onedrive", root, "");
+
+        let mut signer = OneDriveSigner::new();
+        signer.access_token = "token".to_string();
+        signer.expires_in = Timestamp::MAX;
+
+        Arc::new(OneDriveCore {
+            info,
+            capability: Capability::default(),
+            root: root.to_string(),
+            signer: Arc::new(Mutex::new(signer)),
+        })
+    }
+
+    #[test]
+    fn list_request_for_root_targets_drive_root_children() {
+        let core = test_core("/");
+        let request = core.onedrive_list_request("/", None).unwrap();
+        assert_eq!(
+            request.uri().to_string(),
+            format!(
+                "{}/children?{}",
+                OneDriveCore::DRIVE_ROOT_URL,
+                GENERAL_SELECT_PARAM
+            )
+        );
+    }
+
+    #[test]
+    fn list_request_for_nested_path_uses_path_addressing() {
+        let core = test_core("/");
+        let request = core.onedrive_list_request("foo/", Some(10)).unwrap();
+        assert_eq!(
+            request.uri().to_string(),
+            format!(
+                "{}:/foo:/children?{}&$top=10",
+                OneDriveCore::DRIVE_ROOT_URL,
+                GENERAL_SELECT_PARAM
+            )
+        );
+    }
+
+    #[test]
+    fn list_request_for_root_under_custom_root_uses_path_addressing() {
+        let core = test_core("/base/");
+        let request = core.onedrive_list_request("", None).unwrap();
+        assert_eq!(
+            request.uri().to_string(),
+            format!(
+                "{}:/base:/children?{}",
+                OneDriveCore::DRIVE_ROOT_URL,
+                GENERAL_SELECT_PARAM
+            )
+        );
+    }
+
+    #[tokio::test]
+    async fn list_root_returns_entries() {
+        let core = test_core("/");
+        let ctx = test_ctx();
+        let lister = OneDriveLister::new(
+            "/".to_string(),
+            core,
+            ctx,
+            Capability::default(),
+            &OpList::default(),
+        );
+        let mut lister = oio::PageLister::new(lister);
+
+        let mut entries = Vec::new();
+        while let Some(entry) = lister.next().await.unwrap() {
+            entries.push(entry);
+        }
+
+        assert_eq!(entries.len(), 2);
+        assert_eq!(entries[0].mode(), EntryMode::DIR);
+        assert_eq!(entries[1].path(), "test.txt");
+        assert_eq!(entries[1].mode(), EntryMode::FILE);
+    }
+}
+
 mod error {
     use http::Response;
     use http::StatusCode;
diff --git a/core/services/onedrive/src/lister.rs 
b/core/services/onedrive/src/lister.rs
index 58700616f..f5d88bc18 100644
--- a/core/services/onedrive/src/lister.rs
+++ b/core/services/onedrive/src/lister.rs
@@ -25,7 +25,6 @@ use opendal_core::*;
 
 use super::core::OneDriveCore;
 use super::core::parse_error;
-use super::graph_model::GENERAL_SELECT_PARAM;
 use super::graph_model::GraphApiOneDriveListResponse;
 use super::graph_model::ItemType;
 
@@ -59,26 +58,16 @@ impl OneDriveLister {
 
 impl oio::PageList for OneDriveLister {
     async fn next_page(&self, ctx: &mut oio::PageContext) -> Result<()> {
-        let request_url = if ctx.token.is_empty() {
-            let base = format!(
-                "{}:/children?{}",
-                self.core.onedrive_item_url(&self.path, true),
-                GENERAL_SELECT_PARAM
-            );
-            if let Some(limit) = self.op.limit() {
-                base + &format!("&$top={limit}")
-            } else {
-                base
-            }
+        let response = if ctx.token.is_empty() {
+            self.core
+                .onedrive_list(&self.ctx, &self.path, self.op.limit())
+                .await?
         } else {
-            ctx.token.clone()
+            self.core
+                .onedrive_get_next_list_page(&self.ctx, &ctx.token)
+                .await?
         };
 
-        let response = self
-            .core
-            .onedrive_get_next_list_page(&self.ctx, &request_url)
-            .await?;
-
         let status_code = response.status();
         if !status_code.is_success() {
             if status_code == http::StatusCode::NOT_FOUND {

Reply via email to