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

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


The following commit(s) were added to refs/heads/main by this push:
     new 58226a619 feat(bindings/C): add support for list in C binding (#2448)
58226a619 is described below

commit 58226a619da06f9b018955840ff976377a4a86b1
Author: xyJi <[email protected]>
AuthorDate: Fri Jul 14 16:43:50 2023 +0800

    feat(bindings/C): add support for list in C binding (#2448)
    
    * force documentaion, add basic type
    
    * lister and some more func
    
    * add skeleton file for list test
    
    * impl
    
    * test basics
    
    * makefile
    
    * add list tests, the free should also be added
    
    * build and pass
    
    * change to ptr: pt1
    
    * ptr pt2
    
    * some comments in test
    
    * ptr
    
    * works done, need commenting
    
    * remove all box::leak, use box::into_raw for consistency
    
    * comments for opendal_operator_blocking_list
    
    * more comments, and modify free
    
    * format
    
    * crate doc
---
 bindings/c/CONTRIBUTING.md   |  50 ++++++++--------
 bindings/c/Makefile          |   2 +
 bindings/c/README.md         |  19 +++---
 bindings/c/include/opendal.h | 135 +++++++++++++++++++++++++++++++++++++++++++
 bindings/c/src/lib.rs        |  83 +++++++++++++++++++++++++-
 bindings/c/src/result.rs     |  12 ++++
 bindings/c/src/types.rs      | 120 ++++++++++++++++++++++++++++++++------
 bindings/c/tests/bdd.cpp     |   6 +-
 bindings/c/tests/common.hpp  |  42 ++++++++++++++
 bindings/c/tests/list.cpp    | 119 ++++++++++++++++++++++++++++++++++++++
 10 files changed, 532 insertions(+), 56 deletions(-)

diff --git a/bindings/c/CONTRIBUTING.md b/bindings/c/CONTRIBUTING.md
index 1108e46bc..0cf732a37 100644
--- a/bindings/c/CONTRIBUTING.md
+++ b/bindings/c/CONTRIBUTING.md
@@ -1,16 +1,17 @@
 # Contributing
-- [Contributing](#contributing)
-   - [Setup](#setup)
-      - [Using a dev container environment](#using-a-dev-container-environment)
-      - [Bring your own toolbox](#bring-your-own-toolbox)
-   - [Build](#build)
-   - [Test](#test)
-   - [Docs](#docs)
-   - [Misc](#misc)
+
+1. [Contributing](#contributing)
+   1. [Setup](#setup)
+      1. [Using a dev container 
environment](#using-a-dev-container-environment)
+      2. [Bring your own toolbox](#bring-your-own-toolbox)
+   2. [Build](#build)
+   3. [Test](#test)
+   4. [Documentation](#documentation)
 
 ## Setup
 
 ### Using a dev container environment
+
 OpenDAL provides a pre-configured [dev container](https://containers.dev/) 
that could be used in [Github 
Codespaces](https://github.com/features/codespaces), 
[VSCode](https://code.visualstudio.com/), 
[JetBrains](https://www.jetbrains.com/remote-development/gateway/), 
[JuptyerLab](https://jupyterlab.readthedocs.io/en/stable/). Please pick up your 
favourite runtime environment.
 
 The fastest way is:
@@ -18,16 +19,20 @@ The fastest way is:
 [![Open in GitHub 
Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/apache/incubator-opendal?quickstart=1&machine=standardLinux32gb)
 
 ### Bring your own toolbox
+
 To build OpenDAL C binding, the following is all you need:
-- **A C++ compiler** that supports **c++14**, *e.g.* clang++ and g++
+
+- **A C++ compiler** that supports **c++14**, _e.g._ clang++ and g++
 
 - To format the code, you need to install **clang-format**
-    - The `opendal.h` is not formatted by hands when you contribute, please do 
not format the file. **Use `make format` only.**
-    - If your contribution is related to the files under `./tests`, you may 
format it before submitting your pull request. But notice that different 
versions of `clang-format` may format the files differently.
+
+  - The `opendal.h` is not formatted by hands when you contribute, please do 
not format the file. **Use `make format` only.**
+  - If your contribution is related to the files under `./tests`, you may 
format it before submitting your pull request. But notice that different 
versions of `clang-format` may format the files differently.
 
 - **GTest(Google Test)** need to be installed to build the BDD (Behavior 
Driven Development) tests. To see how to build, check 
[here](https://github.com/google/googletest).
 
 For Ubuntu and Debian:
+
 ```shell
 # install C/C++ toolchain
 sudo apt install -y build-essential
@@ -46,43 +51,36 @@ sudo ln -s /usr/lib/libgtest_main.a 
/usr/local/lib/libgtest_main.a
 ```
 
 ## Build
+
 To build the library and header file.
+
 ```shell
 make build
 ```
 
-- The header file `opendal.h` is under `./include` 
+- The header file `opendal.h` is under `./include`
 - The library is under `../../target/debug` after building.
 
 To clean the build results.
+
 ```shell
 make clean
 ```
 
 ## Test
+
 To build and run the tests. (Note that you need to install GTest)
+
 ```shell
 make test
 ```
 
-```text
-[==========] Running 1 test from 1 test suite.
-[----------] Global test environment set-up.
-[----------] 1 test from OpendalBddTest
-[ RUN      ] OpendalBddTest.FeatureTest
-[       OK ] OpendalBddTest.FeatureTest (0 ms)
-[----------] 1 test from OpendalBddTest (0 ms total)
-
-[----------] Global test environment tear-down
-[==========] 1 test from 1 test suite ran. (0 ms total)
-[  PASSED  ] 1 test.
-```
-
 ## Documentation
+
 The documentation index page source is under `./docs/doxygen/html/index.html`.
 If you want to build the documentations yourself, you could use
+
 ```sh
 # this requires you to install doxygen
 make doc
 ```
-
diff --git a/bindings/c/Makefile b/bindings/c/Makefile
index 0d62b6e94..85cb33306 100644
--- a/bindings/c/Makefile
+++ b/bindings/c/Makefile
@@ -41,7 +41,9 @@ build:
 .PHONY: test
 test:
        $(CXX) tests/bdd.cpp -o $(OBJ_DIR)/bdd $(CXXFLAGS) $(LDFLAGS) $(LIBS)
+       $(CXX) tests/list.cpp -o $(OBJ_DIR)/list $(CXXFLAGS) $(LDFLAGS) $(LIBS)
        $(OBJ_DIR)/bdd
+       $(OBJ_DIR)/list
 
 .PHONY: doc
 doc:
diff --git a/bindings/c/README.md b/bindings/c/README.md
index 2fe1fe72f..711361296 100644
--- a/bindings/c/README.md
+++ b/bindings/c/README.md
@@ -3,7 +3,9 @@
 
![](https://github.com/apache/incubator-opendal/assets/5351546/87bbf6e5-f19e-449a-b368-3e283016c887)
 
 ## Example
+
 A simple read and write example
+
 ```C
 #include "assert.h"
 #include "opendal.h"
@@ -44,21 +46,25 @@ int main()
     opendal_operator_free(&op);
 }
 ```
+
 For more examples, please refer to `./examples`
 
 ## Prerequisites
 
 To build OpenDAL C binding, the following is all you need:
-- **A C++ compiler** that supports **c++14**, *e.g.* clang++ and g++
+
+- **A C++ compiler** that supports **c++14**, _e.g._ clang++ and g++
 
 - To format the code, you need to install **clang-format**
-    - The `opendal.h` is not formatted by hands when you contribute, please do 
not format the file. **Use `make format` only.**
-    - If your contribution is related to the files under `./tests`, you may 
format it before submitting your pull request. But notice that different 
versions of `clang-format` may format the files differently.
+
+  - The `opendal.h` is not formatted by hands when you contribute, please do 
not format the file. **Use `make format` only.**
+  - If your contribution is related to the files under `./tests`, you may 
format it before submitting your pull request. But notice that different 
versions of `clang-format` may format the files differently.
 
 - **GTest(Google Test)** need to be installed to build the BDD (Behavior 
Driven Development) tests. To see how to build, check 
[here](https://github.com/google/googletest).
 - (optional) **Doxygen** need to be installed to generate documentations.
 
 For Ubuntu and Debian:
+
 ```shell
 # install C/C++ toolchain
 sudo apt install -y build-essential
@@ -77,6 +83,7 @@ sudo ln -s /usr/lib/libgtest_main.a 
/usr/local/lib/libgtest_main.a
 ```
 
 ## Makefile
+
 - To **build the library and header file**.
 
   ```sh
@@ -87,7 +94,6 @@ sudo ln -s /usr/lib/libgtest_main.a 
/usr/local/lib/libgtest_main.a
 
   - The library is under `../../target/debug` after building.
 
-
 - To **clean** the build results.
 
   ```sh
@@ -106,17 +112,16 @@ sudo ln -s /usr/lib/libgtest_main.a 
/usr/local/lib/libgtest_main.a
   make examples
   ```
 
-
-
 ## Documentation
+
 The documentation index page source is under `./docs/doxygen/html/index.html`.
 If you want to build the documentations yourself, you could use
+
 ```sh
 # this requires you to install doxygen
 make doc
 ```
 
-
 ## License
 
 [Apache v2.0](https://www.apache.org/licenses/LICENSE-2.0)
diff --git a/bindings/c/include/opendal.h b/bindings/c/include/opendal.h
index 73e1cdf7b..c2aa31721 100644
--- a/bindings/c/include/opendal.h
+++ b/bindings/c/include/opendal.h
@@ -81,6 +81,14 @@ typedef enum opendal_code {
   OPENDAL_IS_SAME_FILE,
 } opendal_code;
 
+/**
+ * BlockingLister is designed to list entries at given path in a blocking
+ * manner.
+ *
+ * Users can construct Lister by `blocking_list` or `blocking_scan`.
+ */
+typedef struct BlockingLister BlockingLister;
+
 /**
  * BlockingOperator is the entry for all public blocking APIs.
  *
@@ -113,6 +121,11 @@ typedef enum opendal_code {
  */
 typedef struct BlockingOperator BlockingOperator;
 
+/**
+ * Entry is the file/dir entry returned by `Lister`.
+ */
+typedef struct Entry Entry;
+
 typedef struct HashMap_String__String HashMap_String__String;
 
 /**
@@ -261,6 +274,43 @@ typedef struct opendal_result_stat {
   enum opendal_code code;
 } opendal_result_stat;
 
+/**
+ * \brief BlockingLister is designed to list entries at given path in a 
blocking
+ * manner.
+ *
+ * Users can construct Lister by `blocking_list` or `blocking_scan`(currently 
not supported in C binding)
+ *
+ * For examples, please see the comment section of 
opendal_operator_blocking_list()
+ * @see opendal_operator_blocking_list()
+ */
+typedef struct opendal_blocking_lister {
+  struct BlockingLister *inner;
+} opendal_blocking_lister;
+
+/**
+ * \brief The result type returned by opendal_operator_blocking_list().
+ *
+ * The result type for opendal_operator_blocking_list(), the field `lister` 
contains the lister
+ * of the path, which is an iterator of the objects under the path. the field 
`code` represents
+ * whether the stat operation is successful.
+ */
+typedef struct opendal_result_list {
+  struct opendal_blocking_lister *lister;
+  enum opendal_code code;
+} opendal_result_list;
+
+/**
+ * \brief opendal_list_entry is the entry under a path, which is listed from 
the opendal_blocking_lister
+ *
+ * For examples, please see the comment section of 
opendal_operator_blocking_list()
+ * @see opendal_operator_blocking_list()
+ * @see opendal_list_entry_path()
+ * @see opendal_list_entry_name()
+ */
+typedef struct opendal_list_entry {
+  struct Entry *inner;
+} opendal_list_entry;
+
 #ifdef __cplusplus
 extern "C" {
 #endif // __cplusplus
@@ -531,6 +581,56 @@ struct opendal_result_is_exist 
opendal_operator_is_exist(const struct opendal_op
 struct opendal_result_stat opendal_operator_stat(const struct 
opendal_operator_ptr *ptr,
                                                  const char *path);
 
+/**
+ * \brief Blockingly list the objects in `path`.
+ *
+ * List the object in `path` blockingly by `op_ptr`, return a result with a
+ * opendal_blocking_lister. Users should call opendal_lister_next() on the
+ * lister.
+ *
+ * @param ptr The opendal_operator_ptr created previously
+ * @param path The designated path you want to delete
+ * @see opendal_blocking_lister
+ * @return
+ *
+ * # Example
+ *
+ * Following is an example
+ * ```C
+ * // You have written some data into some files path "root/dir1"
+ * // Your opendal_operator_ptr was called ptr
+ * opendal_result_list l = opendal_operator_blocking_list(ptr, "root/dir1");
+ * assert(l.code == OPENDAL_OK);
+ *
+ * opendal_blocking_lister *lister = l.lister;
+ * opendal_list_entry *entry;
+ *
+ * while ((entry = opendal_lister_next(lister)) != NULL) {
+ *     const char* de_path = opendal_list_entry_path(entry);
+ *     const char* de_name = opendal_list_entry_name(entry);
+ *     // ...... your operations
+ *
+ *     // remember to free the entry after you are done using it
+ *     opendal_list_entry_free(entry);
+ * }
+ *
+ * // and remember to free the lister
+ * opendal_lister_free(lister);
+ * ```
+ *
+ * # Safety
+ *
+ * It is **safe** under the cases below
+ * * The memory pointed to by `path` must contain a valid nul terminator at 
the end of
+ *   the string.
+ *
+ * # Panic
+ *
+ * * If the `path` points to NULL, this function panics, i.e. exits with 
information
+ */
+struct opendal_result_list opendal_operator_blocking_list(const struct 
opendal_operator_ptr *ptr,
+                                                          const char *path);
+
 /**
  * \brief Free the heap-allocated operator pointed by opendal_operator_ptr.
  *
@@ -648,6 +748,41 @@ void opendal_operator_options_set(struct 
opendal_operator_options *self,
  */
 void opendal_operator_options_free(const struct opendal_operator_options 
*options);
 
+/**
+ * \brief Return the next object to be listed
+ *
+ * Lister is an iterator of the objects under its path, this method is the 
same as
+ * calling next() on the iterator
+ *
+ * For examples, please see the comment section of 
opendal_operator_blocking_list()
+ * @see opendal_operator_blocking_list()
+ */
+struct opendal_list_entry *opendal_lister_next(const struct 
opendal_blocking_lister *self);
+
+/**
+ * \brief Free the heap-allocated metadata used by opendal_blocking_lister
+ */
+void opendal_lister_free(const struct opendal_blocking_lister *p);
+
+/**
+ * Path of entry. Path is relative to operator's root.
+ * Only valid in current operator.
+ */
+const char *opendal_list_entry_path(const struct opendal_list_entry *self);
+
+/**
+ * Name of entry. Name is the last segment of path.
+ *
+ * If this entry is a dir, `Name` MUST endswith `/`
+ * Otherwise, `Name` MUST NOT endswith `/`.
+ */
+const char *opendal_list_entry_name(const struct opendal_list_entry *self);
+
+/**
+ * \brief Frees the heap memory used by the opendal_list_entry
+ */
+void opendal_list_entry_free(const struct opendal_list_entry *p);
+
 #ifdef __cplusplus
 } // extern "C"
 #endif // __cplusplus
diff --git a/bindings/c/src/lib.rs b/bindings/c/src/lib.rs
index 7c1c88f43..b749a5d6b 100644
--- a/bindings/c/src/lib.rs
+++ b/bindings/c/src/lib.rs
@@ -15,8 +15,16 @@
 // specific language governing permissions and limitations
 // under the License.
 
+#![warn(missing_docs)]
 #![allow(non_camel_case_types)]
 
+//! The OpenDAL C binding.
+//!
+//! The OpenDAL C binding allows users to utilize the OpenDAL's amazing 
storage accessing capability
+//! in the C programming language.
+//!
+//! For examples, you may see the examples subdirectory
+
 mod error;
 mod result;
 mod types;
@@ -26,6 +34,8 @@ use std::os::raw::c_char;
 use std::str::FromStr;
 
 use ::opendal as od;
+use result::opendal_result_list;
+use types::opendal_blocking_lister;
 
 use crate::error::opendal_code;
 use crate::result::opendal_result_is_exist;
@@ -108,9 +118,9 @@ pub unsafe extern "C" fn opendal_operator_new(
     };
 
     // this prevents the operator memory from being dropped by the Box
-    let op = opendal_operator_ptr::from(Box::leak(Box::new(op)));
+    let op = opendal_operator_ptr::from(Box::into_raw(Box::new(op)));
 
-    Box::leak(Box::new(op))
+    Box::into_raw(Box::new(op))
 }
 
 /// \brief Blockingly write raw bytes to `path`.
@@ -415,3 +425,72 @@ pub unsafe extern "C" fn opendal_operator_stat(
         },
     }
 }
+
+/// \brief Blockingly list the objects in `path`.
+///
+/// List the object in `path` blockingly by `op_ptr`, return a result with a
+/// opendal_blocking_lister. Users should call opendal_lister_next() on the
+/// lister.
+///
+/// @param ptr The opendal_operator_ptr created previously
+/// @param path The designated path you want to delete
+/// @see opendal_blocking_lister
+/// @return
+///
+/// # Example
+///
+/// Following is an example
+/// ```C
+/// // You have written some data into some files path "root/dir1"
+/// // Your opendal_operator_ptr was called ptr
+/// opendal_result_list l = opendal_operator_blocking_list(ptr, "root/dir1");
+/// assert(l.code == OPENDAL_OK);
+///
+/// opendal_blocking_lister *lister = l.lister;
+/// opendal_list_entry *entry;
+///
+/// while ((entry = opendal_lister_next(lister)) != NULL) {
+///     const char* de_path = opendal_list_entry_path(entry);
+///     const char* de_name = opendal_list_entry_name(entry);
+///     // ...... your operations
+///
+///     // remember to free the entry after you are done using it
+///     opendal_list_entry_free(entry);
+/// }
+///
+/// // and remember to free the lister
+/// opendal_lister_free(lister);
+/// ```
+///
+/// # Safety
+///
+/// It is **safe** under the cases below
+/// * The memory pointed to by `path` must contain a valid nul terminator at 
the end of
+///   the string.
+///
+/// # Panic
+///
+/// * If the `path` points to NULL, this function panics, i.e. exits with 
information
+#[no_mangle]
+pub unsafe extern "C" fn opendal_operator_blocking_list(
+    ptr: *const opendal_operator_ptr,
+    path: *const c_char,
+) -> opendal_result_list {
+    if path.is_null() {
+        panic!("The path given is pointing at NULL");
+    }
+
+    let op = (*ptr).as_ref();
+    let path = unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() };
+    match op.list(path) {
+        Ok(lister) => opendal_result_list {
+            lister: 
Box::into_raw(Box::new(opendal_blocking_lister::new(lister))),
+            code: opendal_code::OPENDAL_OK,
+        },
+
+        Err(e) => opendal_result_list {
+            lister: std::ptr::null_mut(),
+            code: opendal_code::from_opendal_error(e),
+        },
+    }
+}
diff --git a/bindings/c/src/result.rs b/bindings/c/src/result.rs
index bb338645f..c0d113718 100644
--- a/bindings/c/src/result.rs
+++ b/bindings/c/src/result.rs
@@ -21,6 +21,7 @@
 //! we are defining all Result types here
 
 use crate::error::opendal_code;
+use crate::types::opendal_blocking_lister;
 use crate::types::opendal_bytes;
 use crate::types::opendal_metadata;
 
@@ -65,3 +66,14 @@ pub struct opendal_result_stat {
     /// The error code, should be OPENDAL_OK if succeeds
     pub code: opendal_code,
 }
+
+/// \brief The result type returned by opendal_operator_blocking_list().
+///
+/// The result type for opendal_operator_blocking_list(), the field `lister` 
contains the lister
+/// of the path, which is an iterator of the objects under the path. the field 
`code` represents
+/// whether the stat operation is successful.
+#[repr(C)]
+pub struct opendal_result_list {
+    pub lister: *mut opendal_blocking_lister,
+    pub code: opendal_code,
+}
diff --git a/bindings/c/src/types.rs b/bindings/c/src/types.rs
index f84f18190..e324b508e 100644
--- a/bindings/c/src/types.rs
+++ b/bindings/c/src/types.rs
@@ -57,9 +57,6 @@ impl opendal_operator_ptr {
     /// ```
     #[no_mangle]
     pub unsafe extern "C" fn opendal_operator_free(op: *const 
opendal_operator_ptr) {
-        if op.is_null() || unsafe { (*op).ptr.is_null() } {
-            return;
-        }
         let _ = unsafe { Box::from_raw((*op).ptr as *mut od::BlockingOperator) 
};
         let _ = unsafe { Box::from_raw(op as *mut opendal_operator_ptr) };
     }
@@ -73,15 +70,15 @@ impl opendal_operator_ptr {
 }
 
 #[allow(clippy::from_over_into)]
-impl From<&od::BlockingOperator> for opendal_operator_ptr {
-    fn from(value: &od::BlockingOperator) -> Self {
+impl From<*const od::BlockingOperator> for opendal_operator_ptr {
+    fn from(value: *const od::BlockingOperator) -> Self {
         Self { ptr: value }
     }
 }
 
 #[allow(clippy::from_over_into)]
-impl From<&mut od::BlockingOperator> for opendal_operator_ptr {
-    fn from(value: &mut od::BlockingOperator) -> Self {
+impl From<*mut od::BlockingOperator> for opendal_operator_ptr {
+    fn from(value: *mut od::BlockingOperator) -> Self {
         Self { ptr: value }
     }
 }
@@ -159,13 +156,10 @@ impl opendal_metadata {
     /// \brief Free the heap-allocated metadata used by opendal_metadata
     #[no_mangle]
     pub extern "C" fn opendal_metadata_free(&mut self) {
-        if !self.inner.is_null() {
-            unsafe {
-                mem::drop(Box::from_raw(self.inner));
-            }
+        unsafe {
+            mem::drop(Box::from_raw(self.inner));
+            mem::drop(Box::from_raw(self as *mut Self));
         }
-
-        unsafe { mem::drop(Box::from_raw(self as *mut Self)) }
     }
 
     /// \brief Return the content_length of the metadata
@@ -256,10 +250,10 @@ impl opendal_operator_options {
     pub extern "C" fn opendal_operator_options_new() -> *mut Self {
         let map: HashMap<String, String> = HashMap::default();
         let options = Self {
-            inner: Box::leak(Box::new(map)),
+            inner: Box::into_raw(Box::new(map)),
         };
 
-        Box::leak(Box::new(options))
+        Box::into_raw(Box::new(options))
     }
 
     /// \brief Set a Key-Value pair inside opendal_operator_options
@@ -306,10 +300,100 @@ impl opendal_operator_options {
     pub unsafe extern "C" fn opendal_operator_options_free(
         options: *const opendal_operator_options,
     ) {
-        if options.is_null() || unsafe { (*options).inner.is_null() } {
-            return;
-        }
         let _ = unsafe { Box::from_raw((*options).inner as *mut 
HashMap<String, String>) };
         let _ = unsafe { Box::from_raw(options as *mut 
opendal_operator_options) };
     }
 }
+
+/// \brief BlockingLister is designed to list entries at given path in a 
blocking
+/// manner.
+///
+/// Users can construct Lister by `blocking_list` or `blocking_scan`(currently 
not supported in C binding)
+///
+/// For examples, please see the comment section of 
opendal_operator_blocking_list()
+/// @see opendal_operator_blocking_list()
+#[repr(C)]
+pub struct opendal_blocking_lister {
+    inner: *mut od::BlockingLister,
+}
+
+impl opendal_blocking_lister {
+    pub(crate) fn new(lister: od::BlockingLister) -> Self {
+        Self {
+            inner: Box::into_raw(Box::new(lister)),
+        }
+    }
+
+    /// \brief Return the next object to be listed
+    ///
+    /// Lister is an iterator of the objects under its path, this method is 
the same as
+    /// calling next() on the iterator
+    ///
+    /// For examples, please see the comment section of 
opendal_operator_blocking_list()
+    /// @see opendal_operator_blocking_list()
+    #[no_mangle]
+    pub unsafe extern "C" fn opendal_lister_next(&self) -> *mut 
opendal_list_entry {
+        let e = (*self.inner).next();
+        if e.is_none() {
+            return std::ptr::null_mut();
+        }
+
+        match e.unwrap() {
+            Ok(e) => Box::into_raw(Box::new(opendal_list_entry::new(e))),
+            Err(_) => std::ptr::null_mut(),
+        }
+    }
+
+    /// \brief Free the heap-allocated metadata used by opendal_blocking_lister
+    #[no_mangle]
+    pub unsafe extern "C" fn opendal_lister_free(p: *const 
opendal_blocking_lister) {
+        unsafe {
+            let _ = Box::from_raw((*p).inner as *mut od::BlockingLister);
+            let _ = Box::from_raw(p as *mut opendal_blocking_lister);
+        }
+    }
+}
+
+/// \brief opendal_list_entry is the entry under a path, which is listed from 
the opendal_blocking_lister
+///
+/// For examples, please see the comment section of 
opendal_operator_blocking_list()
+/// @see opendal_operator_blocking_list()
+/// @see opendal_list_entry_path()
+/// @see opendal_list_entry_name()
+#[repr(C)]
+pub struct opendal_list_entry {
+    inner: *mut od::Entry,
+}
+
+impl opendal_list_entry {
+    /// Used to convert the Rust type into C type
+    pub(crate) fn new(entry: od::Entry) -> Self {
+        Self {
+            inner: Box::into_raw(Box::new(entry)),
+        }
+    }
+
+    /// Path of entry. Path is relative to operator's root.
+    /// Only valid in current operator.
+    #[no_mangle]
+    pub unsafe extern "C" fn opendal_list_entry_path(&self) -> *const c_char {
+        (*self.inner).path().as_ptr() as *const c_char
+    }
+
+    /// Name of entry. Name is the last segment of path.
+    ///
+    /// If this entry is a dir, `Name` MUST endswith `/`
+    /// Otherwise, `Name` MUST NOT endswith `/`.
+    #[no_mangle]
+    pub unsafe extern "C" fn opendal_list_entry_name(&self) -> *const c_char {
+        (*self.inner).name().as_ptr() as *const c_char
+    }
+
+    /// \brief Frees the heap memory used by the opendal_list_entry
+    #[no_mangle]
+    pub unsafe extern "C" fn opendal_list_entry_free(p: *const 
opendal_list_entry) {
+        unsafe {
+            let _ = Box::from_raw(p as *mut opendal_list_entry);
+        }
+    }
+}
diff --git a/bindings/c/tests/bdd.cpp b/bindings/c/tests/bdd.cpp
index cac9a11a0..c7d797172 100644
--- a/bindings/c/tests/bdd.cpp
+++ b/bindings/c/tests/bdd.cpp
@@ -25,7 +25,7 @@ extern "C" {
 
 class OpendalBddTest : public ::testing::Test {
 protected:
-    const opendal_operator_ptr *p;
+    const opendal_operator_ptr* p;
 
     std::string scheme;
     std::string path;
@@ -37,7 +37,7 @@ protected:
         this->path = std::string("test");
         this->content = std::string("Hello, World!");
 
-        opendal_operator_options *options = opendal_operator_options_new();
+        opendal_operator_options* options = opendal_operator_options_new();
         opendal_operator_options_set(options, "root", "/myroot");
 
         // Given A new OpenDAL Blocking Operator
@@ -69,7 +69,7 @@ TEST_F(OpendalBddTest, FeatureTest)
     // The blocking file "test" entry mode must be file
     opendal_result_stat s = opendal_operator_stat(this->p, this->path.c_str());
     EXPECT_EQ(s.code, OPENDAL_OK);
-    opendal_metadata *meta = s.meta;
+    opendal_metadata* meta = s.meta;
     EXPECT_TRUE(opendal_metadata_is_file(meta));
 
     // The blocking file "test" content length must be 13
diff --git a/bindings/c/tests/common.hpp b/bindings/c/tests/common.hpp
new file mode 100644
index 000000000..b8bf2e9b3
--- /dev/null
+++ b/bindings/c/tests/common.hpp
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include <vector>
+#include <random>
+
+std::vector<unsigned char> generateRandomBytes(std::size_t size) {
+  // Create a random device to generate random numbers
+  std::random_device rd;
+
+  // Create a random engine and seed it with the random device
+  std::mt19937_64 gen(rd());
+
+  // Create a distribution to produce random bytes
+  std::uniform_int_distribution<unsigned char> dist(0, 255);
+
+  // Create a vector to hold the random bytes
+  std::vector<unsigned char> randomBytes(size);
+
+  // Generate random bytes and store them in the vector
+  for (std::size_t i = 0; i < size; ++i) {
+    randomBytes[i] = dist(gen);
+  }
+
+  return randomBytes;
+}
diff --git a/bindings/c/tests/list.cpp b/bindings/c/tests/list.cpp
new file mode 100644
index 000000000..de905f17d
--- /dev/null
+++ b/bindings/c/tests/list.cpp
@@ -0,0 +1,119 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+#include "common.hpp"
+#include "gtest/gtest.h"
+#include <cstring>
+
+extern "C" {
+#include "opendal.h"
+}
+
+class OpendalListTest : public ::testing::Test {
+protected:
+    const opendal_operator_ptr* p;
+
+    // set up a brand new operator
+    void SetUp() override
+    {
+        opendal_operator_options* options = opendal_operator_options_new();
+        opendal_operator_options_set(options, "root", "/myroot");
+
+        this->p = opendal_operator_new("memory", options);
+        EXPECT_TRUE(this->p->ptr);
+
+        opendal_operator_options_free(options);
+    }
+
+    void TearDown() override { opendal_operator_free(this->p); }
+};
+
+// Basic usecase of list
+TEST_F(OpendalListTest, ListDirTest)
+{
+    std::string dname = "some_random_dir_name_152312dbfas";
+    std::string fname = "some_random_file_name_2138912rbf";
+
+    // 4 MiB of random bytes
+    uintptr_t nbytes = 4 * 1024 * 1024;
+    auto rand = generateRandomBytes(4 * 1024 * 1024);
+    auto random_bytes = reinterpret_cast<uint8_t*>(rand.data());
+
+    std::string path = dname + "/" + fname;
+    opendal_bytes data = {
+        .data = random_bytes,
+        .len = nbytes,
+    };
+
+    // write must succeed
+    EXPECT_EQ(opendal_operator_blocking_write(this->p, path.c_str(), data),
+        OPENDAL_OK);
+
+    // list must succeed since the write succeeded
+    opendal_result_list l = opendal_operator_blocking_list(this->p, (dname + 
"/").c_str());
+    EXPECT_EQ(l.code, OPENDAL_OK);
+
+    opendal_blocking_lister* lister = l.lister;
+
+    // start checking the lister's result
+    bool found = false;
+
+    opendal_list_entry* entry = opendal_lister_next(lister);
+    while (entry) {
+        const char* de_path = opendal_list_entry_path(entry);
+
+        // stat must succeed
+        opendal_result_stat s = opendal_operator_stat(this->p, de_path);
+        EXPECT_EQ(s.code, OPENDAL_OK);
+
+        opendal_metadata* meta = s.meta;
+
+        if (!strcmp(de_path, path.c_str())) {
+            found = true;
+
+            // the path we found has to be a file, and the length must be 
coherent
+            EXPECT_TRUE(opendal_metadata_is_file(meta));
+            EXPECT_EQ(opendal_metadata_content_length(meta), nbytes);
+        }
+
+        opendal_list_entry_free(entry);
+        entry = opendal_lister_next(lister);
+    }
+
+    // we must have found the file we wrote
+    EXPECT_TRUE(found);
+
+    // delete
+    EXPECT_EQ(opendal_operator_blocking_delete(this->p, path.c_str()),
+        OPENDAL_OK);
+
+    opendal_lister_free(lister);
+}
+
+// todo: Try list an empty directory
+TEST_F(OpendalListTest, ListEmptyDirTest) { }
+
+// todo: Try list a directory that does not exist
+TEST_F(OpendalListTest, ListNotExistDirTest) { }
+
+int main(int argc, char** argv)
+{
+    ::testing::InitGoogleTest(&argc, argv);
+    return RUN_ALL_TESTS();
+}

Reply via email to