This is an automated email from the ASF dual-hosted git repository.
yuchanns 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 d4cb4738b feat(binding/go): Add ListOptions with recursive support
(#7605)
d4cb4738b is described below
commit d4cb4738b37eddbf4f1d005d2c1da591c7f0cf6c
Author: PoAn Yang <[email protected]>
AuthorDate: Thu May 28 21:58:37 2026 +0900
feat(binding/go): Add ListOptions with recursive support (#7605)
---
bindings/c/include/opendal.h | 70 ++++++++++++++
bindings/c/src/lib.rs | 1 +
bindings/c/src/operator.rs | 52 +++++++++++
bindings/c/src/types.rs | 52 +++++++++++
bindings/c/tests/test_framework.h | 9 ++
bindings/c/tests/test_suites_list.cpp | 125 +++++++++++++++++++++++++
bindings/go/lister.go | 126 +++++++++++++++++++-------
bindings/go/string_ownership_test.go | 42 +++++++++
bindings/go/tests/behavior_tests/list_test.go | 69 +++++++++++++-
bindings/go/types.go | 2 +
10 files changed, 513 insertions(+), 35 deletions(-)
diff --git a/bindings/c/include/opendal.h b/bindings/c/include/opendal.h
index 10e8642f3..493c62c12 100644
--- a/bindings/c/include/opendal.h
+++ b/bindings/c/include/opendal.h
@@ -439,6 +439,25 @@ typedef struct opendal_result_list {
struct opendal_error *error;
} opendal_result_list;
+/**
+ * \brief The options for the list operation.
+ *
+ * This struct carries the options for the list operation, including whether to
+ * list recursively. Use `opendal_list_options_new()` to construct and
+ * `opendal_list_options_free()` to free.
+ *
+ * @see opendal_operator_list_with
+ * @see opendal_list_options_new
+ * @see opendal_list_options_free
+ * @see opendal_list_options_set_recursive
+ */
+typedef struct opendal_list_options {
+ /**
+ * Whether to list recursively under the prefix; default false.
+ */
+ bool recursive;
+} opendal_list_options;
+
/**
* \brief Metadata for **operator**, users can use this metadata to get
information
* of operator.
@@ -1281,6 +1300,33 @@ struct opendal_result_stat opendal_operator_stat(const
struct opendal_operator *
struct opendal_result_list opendal_operator_list(const struct opendal_operator
*op,
const char *path);
+/**
+ * \brief Blocking list the objects in `path` with options.
+ *
+ * List the objects in `path` with the provided `opendal_list_options`. This is
+ * similar to `opendal_operator_list` but allows passing options such as
+ * `recursive` to control the listing behavior.
+ *
+ * @param op The opendal_operator created previously
+ * @param path The designated path you want to list
+ * @param opts The options for the list operation; pass NULL to use defaults
+ * @see opendal_lister
+ * @see opendal_list_options
+ * @return Returns opendal_result_list, containing a lister and an
opendal_error.
+ *
+ * # Safety
+ *
+ * * The memory pointed to by `path` must contain a valid null 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_list_with(const struct
opendal_operator *op,
+ const char *path,
+ const struct
opendal_list_options *opts);
+
/**
* \brief Blocking create the directory in `path`.
*
@@ -1537,6 +1583,30 @@ void opendal_string_free(char *ptr);
*/
void opendal_bytes_free(struct opendal_bytes *ptr);
+/**
+ * \brief Construct a heap-allocated opendal_list_options with default values.
+ *
+ * @return A new opendal_list_options with all options set to their defaults.
+ *
+ * @see opendal_list_options_free
+ */
+struct opendal_list_options *opendal_list_options_new(void);
+
+/**
+ * \brief Set the recursive option.
+ *
+ * @param opts The opendal_list_options to modify.
+ * @param recursive Whether to list recursively.
+ */
+void opendal_list_options_set_recursive(struct opendal_list_options *opts,
bool recursive);
+
+/**
+ * \brief Free the heap memory used by opendal_list_options.
+ *
+ * @param opts The opendal_list_options to free.
+ */
+void opendal_list_options_free(struct opendal_list_options *opts);
+
/**
* \brief Construct a heap-allocated opendal_operator_options
*
diff --git a/bindings/c/src/lib.rs b/bindings/c/src/lib.rs
index ede881e03..89fd461a3 100644
--- a/bindings/c/src/lib.rs
+++ b/bindings/c/src/lib.rs
@@ -70,6 +70,7 @@ pub use result::opendal_result_writer_write;
mod types;
pub use types::opendal_bytes;
+pub use types::opendal_list_options;
pub use types::opendal_operator_options;
mod entry;
diff --git a/bindings/c/src/operator.rs b/bindings/c/src/operator.rs
index bb6de2f5a..4b3a058d1 100644
--- a/bindings/c/src/operator.rs
+++ b/bindings/c/src/operator.rs
@@ -767,6 +767,58 @@ pub unsafe extern "C" fn opendal_operator_list(
}
}
+/// \brief Blocking list the objects in `path` with options.
+///
+/// List the objects in `path` with the provided `opendal_list_options`. This
is
+/// similar to `opendal_operator_list` but allows passing options such as
+/// `recursive` to control the listing behavior.
+///
+/// @param op The opendal_operator created previously
+/// @param path The designated path you want to list
+/// @param opts The options for the list operation; pass NULL to use defaults
+/// @see opendal_lister
+/// @see opendal_list_options
+/// @return Returns opendal_result_list, containing a lister and an
opendal_error.
+///
+/// # Safety
+///
+/// * The memory pointed to by `path` must contain a valid null 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_list_with(
+ op: &opendal_operator,
+ path: *const c_char,
+ opts: *const opendal_list_options,
+) -> opendal_result_list {
+ assert!(!path.is_null());
+ let path = std::ffi::CStr::from_ptr(path)
+ .to_str()
+ .expect("malformed path");
+ let list_opts = if opts.is_null() {
+ core::options::ListOptions::default()
+ } else {
+ let o = &*opts;
+ core::options::ListOptions {
+ recursive: o.recursive,
+ ..Default::default()
+ }
+ };
+ match op.deref().lister_options(path, list_opts) {
+ Ok(lister) => opendal_result_list {
+ lister: Box::into_raw(Box::new(opendal_lister::new(lister))),
+ error: std::ptr::null_mut(),
+ },
+ Err(e) => opendal_result_list {
+ lister: std::ptr::null_mut(),
+ error: opendal_error::new(e),
+ },
+ }
+}
+
/// \brief Blocking create the directory in `path`.
///
/// Create the directory in `path` blocking by `op_ptr`.
diff --git a/bindings/c/src/types.rs b/bindings/c/src/types.rs
index 2ba5b6a5b..1b63f24ce 100644
--- a/bindings/c/src/types.rs
+++ b/bindings/c/src/types.rs
@@ -84,6 +84,58 @@ impl opendal_bytes {
}
}
+/// \brief The options for the list operation.
+///
+/// This struct carries the options for the list operation, including whether
to
+/// list recursively. Use `opendal_list_options_new()` to construct and
+/// `opendal_list_options_free()` to free.
+///
+/// @see opendal_operator_list_with
+/// @see opendal_list_options_new
+/// @see opendal_list_options_free
+/// @see opendal_list_options_set_recursive
+#[repr(C)]
+pub struct opendal_list_options {
+ /// Whether to list recursively under the prefix; default false.
+ pub recursive: bool,
+}
+
+impl opendal_list_options {
+ /// \brief Construct a heap-allocated opendal_list_options with default
values.
+ ///
+ /// @return A new opendal_list_options with all options set to their
defaults.
+ ///
+ /// @see opendal_list_options_free
+ #[no_mangle]
+ pub extern "C" fn opendal_list_options_new() -> *mut Self {
+ Box::into_raw(Box::new(Self { recursive: false }))
+ }
+
+ /// \brief Set the recursive option.
+ ///
+ /// @param opts The opendal_list_options to modify.
+ /// @param recursive Whether to list recursively.
+ #[no_mangle]
+ pub unsafe extern "C" fn opendal_list_options_set_recursive(
+ opts: *mut opendal_list_options,
+ recursive: bool,
+ ) {
+ if !opts.is_null() {
+ (*opts).recursive = recursive;
+ }
+ }
+
+ /// \brief Free the heap memory used by opendal_list_options.
+ ///
+ /// @param opts The opendal_list_options to free.
+ #[no_mangle]
+ pub unsafe extern "C" fn opendal_list_options_free(opts: *mut
opendal_list_options) {
+ if !opts.is_null() {
+ drop(Box::from_raw(opts));
+ }
+ }
+}
+
impl Drop for opendal_bytes {
fn drop(&mut self) {
unsafe {
diff --git a/bindings/c/tests/test_framework.h
b/bindings/c/tests/test_framework.h
index 06f60c27c..e7abe6284 100644
--- a/bindings/c/tests/test_framework.h
+++ b/bindings/c/tests/test_framework.h
@@ -212,6 +212,15 @@ inline opendal_required_capability
make_capability_create_dir_list() {
return cap;
}
+inline opendal_required_capability
make_capability_write_create_dir_list_recursive() {
+ opendal_required_capability cap = NO_CAPABILITY;
+ cap.write = true;
+ cap.create_dir = true;
+ cap.list = true;
+ cap.list_with_recursive = true;
+ return cap;
+}
+
inline opendal_required_capability make_capability_presign() {
opendal_required_capability cap = NO_CAPABILITY;
cap.read = true;
diff --git a/bindings/c/tests/test_suites_list.cpp
b/bindings/c/tests/test_suites_list.cpp
index 12c291462..33aebe7ab 100644
--- a/bindings/c/tests/test_suites_list.cpp
+++ b/bindings/c/tests/test_suites_list.cpp
@@ -20,6 +20,7 @@
#include "test_framework.h"
#include <set>
#include <string>
+#include <unordered_set>
// Test: Basic list operation
void test_list_basic(opendal_test_context* ctx)
@@ -347,6 +348,126 @@ void test_entry_metadata(opendal_test_context* ctx)
opendal_operator_delete(ctx->config->operator_instance, dir_path);
}
+// Test: list_with default options (null opts behaves like list)
+void test_list_with_default_options(opendal_test_context* ctx)
+{
+ const char* dir_path = "test_list_with_default/";
+ const char* file_path = "test_list_with_default/file.txt";
+
+ opendal_error* error =
opendal_operator_create_dir(ctx->config->operator_instance, dir_path);
+ OPENDAL_ASSERT_NO_ERROR(error, "Create dir should succeed");
+
+ opendal_bytes data;
+ data.data = (uint8_t*)"content";
+ data.len = 7;
+ data.capacity = 7;
+ error = opendal_operator_write(ctx->config->operator_instance, file_path,
&data);
+ OPENDAL_ASSERT_NO_ERROR(error, "Write should succeed");
+
+ // Pass NULL opts — should behave identically to opendal_operator_list
+ opendal_result_list list_result = opendal_operator_list_with(
+ ctx->config->operator_instance, dir_path, NULL);
+ OPENDAL_ASSERT_NO_ERROR(list_result.error, "list_with(NULL opts) should
succeed");
+ OPENDAL_ASSERT_NOT_NULL(list_result.lister, "Lister should not be null");
+
+ bool found_file = false;
+ while (true) {
+ opendal_result_lister_next next =
opendal_lister_next(list_result.lister);
+ if (next.error) {
+ OPENDAL_ASSERT_NO_ERROR(next.error, "lister_next should not fail");
+ break;
+ }
+ if (!next.entry) break;
+
+ char* path = opendal_entry_path(next.entry);
+ if (strcmp(path, file_path) == 0) {
+ found_file = true;
+ }
+ opendal_string_free(path);
+ opendal_entry_free(next.entry);
+ }
+
+ OPENDAL_ASSERT(found_file, "Should find the file with null opts");
+
+ opendal_lister_free(list_result.lister);
+ opendal_operator_delete(ctx->config->operator_instance, file_path);
+ opendal_operator_delete(ctx->config->operator_instance, dir_path);
+}
+
+// Test: list_with recursive=true returns entries in nested directories
+void test_list_with_recursive(opendal_test_context* ctx)
+{
+ const char* base_dir = "test_list_recursive/";
+ const char* sub_dir = "test_list_recursive/sub/";
+ const char* deep_dir = "test_list_recursive/sub/deep/";
+ const char* file_top = "test_list_recursive/top.txt";
+ const char* file_sub = "test_list_recursive/sub/mid.txt";
+ const char* file_deep = "test_list_recursive/sub/deep/bottom.txt";
+
+ opendal_bytes data;
+ data.data = (uint8_t*)"x";
+ data.len = 1;
+ data.capacity = 1;
+
+ opendal_error* error;
+ error = opendal_operator_create_dir(ctx->config->operator_instance,
base_dir);
+ OPENDAL_ASSERT_NO_ERROR(error, "Create base dir should succeed");
+ error = opendal_operator_create_dir(ctx->config->operator_instance,
sub_dir);
+ OPENDAL_ASSERT_NO_ERROR(error, "Create sub dir should succeed");
+ error = opendal_operator_create_dir(ctx->config->operator_instance,
deep_dir);
+ OPENDAL_ASSERT_NO_ERROR(error, "Create deep dir should succeed");
+ error = opendal_operator_write(ctx->config->operator_instance, file_top,
&data);
+ OPENDAL_ASSERT_NO_ERROR(error, "Write top file should succeed");
+ error = opendal_operator_write(ctx->config->operator_instance, file_sub,
&data);
+ OPENDAL_ASSERT_NO_ERROR(error, "Write sub file should succeed");
+ error = opendal_operator_write(ctx->config->operator_instance, file_deep,
&data);
+ OPENDAL_ASSERT_NO_ERROR(error, "Write deep file should succeed");
+
+ // List recursively from base_dir
+ opendal_list_options* opts = opendal_list_options_new();
+ OPENDAL_ASSERT_NOT_NULL(opts, "list_options_new should not return NULL");
+ opendal_list_options_set_recursive(opts, true);
+
+ opendal_result_list list_result = opendal_operator_list_with(
+ ctx->config->operator_instance, base_dir, opts);
+ opendal_list_options_free(opts);
+
+ OPENDAL_ASSERT_NO_ERROR(list_result.error, "Recursive list should
succeed");
+ OPENDAL_ASSERT_NOT_NULL(list_result.lister, "Lister should not be null");
+
+ std::unordered_set<std::string> found_paths;
+ while (true) {
+ opendal_result_lister_next next =
opendal_lister_next(list_result.lister);
+ if (next.error) {
+ OPENDAL_ASSERT_NO_ERROR(next.error, "lister_next should not fail");
+ break;
+ }
+ if (!next.entry) break;
+
+ char* path = opendal_entry_path(next.entry);
+ found_paths.insert(std::string(path));
+ opendal_string_free(path);
+ opendal_entry_free(next.entry);
+ }
+ opendal_lister_free(list_result.lister);
+
+ // All three files must appear in a recursive listing
+ OPENDAL_ASSERT(found_paths.count(file_top) > 0,
+ "Recursive list must include top-level file");
+ OPENDAL_ASSERT(found_paths.count(file_sub) > 0,
+ "Recursive list must include file in sub directory");
+ OPENDAL_ASSERT(found_paths.count(file_deep) > 0,
+ "Recursive list must include file in deep directory");
+
+ // Cleanup
+ opendal_operator_delete(ctx->config->operator_instance, file_deep);
+ opendal_operator_delete(ctx->config->operator_instance, file_sub);
+ opendal_operator_delete(ctx->config->operator_instance, file_top);
+ opendal_operator_delete(ctx->config->operator_instance, deep_dir);
+ opendal_operator_delete(ctx->config->operator_instance, sub_dir);
+ opendal_operator_delete(ctx->config->operator_instance, base_dir);
+}
+
// Define the list test suite
opendal_test_case list_tests[] = {
{ "list_basic", test_list_basic, make_capability_write_create_dir_list() },
@@ -356,6 +477,10 @@ opendal_test_case list_tests[] = {
make_capability_write_create_dir_list() },
{ "entry_metadata", test_entry_metadata,
make_capability_write_create_dir_list() },
+ { "list_with_default_options", test_list_with_default_options,
+ make_capability_write_create_dir_list() },
+ { "list_with_recursive", test_list_with_recursive,
+ make_capability_write_create_dir_list_recursive() },
};
opendal_test_suite list_suite = {
diff --git a/bindings/go/lister.go b/bindings/go/lister.go
index 9d7943dea..fc6fd2b33 100644
--- a/bindings/go/lister.go
+++ b/bindings/go/lister.go
@@ -74,69 +74,86 @@ func (op *Operator) Check() (err error) {
// List returns a Lister to iterate over entries that start with the given
path in the parent directory.
//
-// This function creates a new Lister to enumerate entries in the specified
path.
+// WithListFn is a functional option for the List operation.
+type WithListFn func(*listOptions)
+
+// ListWithRecursive sets the recursive flag for the list operation.
+//
+// When recursive is true, the list operation will descend into
sub-directories.
+func ListWithRecursive(recursive bool) WithListFn {
+ return func(o *listOptions) {
+ o.recursive = recursive
+ }
+}
+
+// listOptions holds the options for a list operation.
+type listOptions struct {
+ recursive bool
+}
+
+// List returns a Lister to iterate over entries that start with the given
path.
//
// # Parameters
//
// - path: The starting path for listing entries.
+// - opts: Optional functional options to configure the list operation.
//
// # Returns
//
// - *Lister: A new Lister instance for iterating over entries.
// - error: An error if the listing operation fails, or nil if successful.
//
-// # Notes
-//
-// 1. List is a wrapper around the C-binding function
`opendal_operator_list`. Recursive listing is not currently supported.
-// 2. Returned entries do not include metadata information. Use op.Stat to
fetch metadata for individual entries.
-//
// # Example
//
// func exampleList(op *opendal.Operator) {
-// lister, err := op.List("test")
+// // List without options
+// lister, err := op.List("test/")
// if err != nil {
// log.Fatal(err)
// }
+// defer lister.Close()
+//
+// // List with recursive option
+// lister, err = op.List("test/", opendal.ListWithRecursive(true))
+// if err != nil {
+// log.Fatal(err)
+// }
+// defer lister.Close()
//
// for lister.Next() {
// entry := lister.Entry()
-//
-// fmt.Printf("Name: %s\n", entry.Name())
-// if meta := entry.Metadata(); meta != nil {
-// fmt.Printf("Length: %d\n", meta.ContentLength())
-// fmt.Printf("Last Modified: %s\n",
meta.LastModified())
-// fmt.Printf("Is Directory: %v, Is File: %v\n",
meta.IsDir(), meta.IsFile())
-// }
-// fmt.Println("---")
+// fmt.Printf("Path: %s\n", entry.Path())
// }
-// if err := lister.Err(); err != nil {
+// if err := lister.Error(); err != nil {
// log.Printf("Error during listing: %v", err)
// }
// }
//
-// Note: Always check lister.Err() after the loop to catch any errors that
+// Note: Always check lister.Error() after the loop to catch any errors that
// occurred during iteration.
-func (op *Operator) List(path string) (*Lister, error) {
- inner, err := ffiOperatorList.symbol(op.ctx)(op.inner, path)
+func (op *Operator) List(path string, opts ...WithListFn) (*Lister, error) {
+ o := &listOptions{}
+ for _, opt := range opts {
+ opt(o)
+ }
+ cOpts := ffiListOptionsNew.symbol(op.ctx)()
+ defer ffiListOptionsFree.symbol(op.ctx)(cOpts)
+ ffiListOptionsSetRecursive.symbol(op.ctx)(cOpts, o.recursive)
+ listerInner, err := ffiOperatorListWith.symbol(op.ctx)(op.inner, path,
cOpts)
if err != nil {
return nil, err
}
- lister := &Lister{
- inner: inner,
+ return &Lister{
+ inner: listerInner,
ctx: op.ctx,
- }
- return lister, nil
+ }, nil
}
-// Lister provides an mechanism for listing entries at a specified path.
+// Lister provides a mechanism for listing entries at a specified path.
//
// Lister is a wrapper around the C-binding function `opendal_operator_list`.
It allows
// for efficient iteration over entries in a storage system.
//
-// # Limitations
-//
-// - The current implementation does not support the `list_with`
functionality.
-//
// # Usage
//
// Lister should be used in conjunction with its Next() and Entry() methods to
@@ -303,12 +320,54 @@ func (e *Entry) Metadata() *Metadata {
return e.meta
}
-var ffiOperatorList = newFFI(ffiOpts{
- sym: "opendal_operator_list",
+var ffiListOptionsNew = newFFI(ffiOpts{
+ sym: "opendal_list_options_new",
+ rType: &ffi.TypePointer,
+}, func(_ context.Context, ffiCall ffiCall) func() *opendalListOptions {
+ return func() *opendalListOptions {
+ var opts *opendalListOptions
+ ffiCall(unsafe.Pointer(&opts))
+ return opts
+ }
+})
+
+var ffiListOptionsSetRecursive = newFFI(ffiOpts{
+ sym: "opendal_list_options_set_recursive",
+ rType: &ffi.TypeVoid,
+ aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypeUint8},
+}, func(_ context.Context, ffiCall ffiCall) func(opts *opendalListOptions,
recursive bool) {
+ return func(opts *opendalListOptions, recursive bool) {
+ var r uint8
+ if recursive {
+ r = 1
+ }
+ ffiCall(
+ nil,
+ unsafe.Pointer(&opts),
+ unsafe.Pointer(&r),
+ )
+ }
+})
+
+var ffiListOptionsFree = newFFI(ffiOpts{
+ sym: "opendal_list_options_free",
+ rType: &ffi.TypeVoid,
+ aTypes: []*ffi.Type{&ffi.TypePointer},
+}, func(_ context.Context, ffiCall ffiCall) func(opts *opendalListOptions) {
+ return func(opts *opendalListOptions) {
+ ffiCall(
+ nil,
+ unsafe.Pointer(&opts),
+ )
+ }
+})
+
+var ffiOperatorListWith = newFFI(ffiOpts{
+ sym: "opendal_operator_list_with",
rType: &typeResultList,
- aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer},
-}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path
string) (*opendalLister, error) {
- return func(op *opendalOperator, path string) (*opendalLister, error) {
+ aTypes: []*ffi.Type{&ffi.TypePointer, &ffi.TypePointer,
&ffi.TypePointer},
+}, func(ctx context.Context, ffiCall ffiCall) func(op *opendalOperator, path
string, opts *opendalListOptions) (*opendalLister, error) {
+ return func(op *opendalOperator, path string, opts *opendalListOptions)
(*opendalLister, error) {
bytePath, err := BytePtrFromString(path)
if err != nil {
return nil, err
@@ -318,6 +377,7 @@ var ffiOperatorList = newFFI(ffiOpts{
unsafe.Pointer(&result),
unsafe.Pointer(&op),
unsafe.Pointer(&bytePath),
+ unsafe.Pointer(&opts),
)
if result.err != nil {
return nil, parseError(ctx, result.err)
diff --git a/bindings/go/string_ownership_test.go
b/bindings/go/string_ownership_test.go
index 65da9ecbe..368efe037 100644
--- a/bindings/go/string_ownership_test.go
+++ b/bindings/go/string_ownership_test.go
@@ -276,3 +276,45 @@ func assertFreedPointers(t *testing.T, got []*byte, want
...*byte) {
}
}
}
+
+func TestListWithRecursiveDefaultNotRecursive(t *testing.T) {
+ o := &listOptions{}
+ if o.recursive {
+ t.Fatalf("default listOptions.recursive = true, want false")
+ }
+}
+
+func TestListWithRecursiveTrue(t *testing.T) {
+ o := &listOptions{}
+ ListWithRecursive(true)(o)
+ if !o.recursive {
+ t.Fatalf("ListWithRecursive(true): recursive = false, want
true")
+ }
+}
+
+func TestListWithRecursiveFalse(t *testing.T) {
+ o := &listOptions{}
+ ListWithRecursive(true)(o)
+ ListWithRecursive(false)(o)
+ if o.recursive {
+ t.Fatalf("ListWithRecursive(false): recursive = true, want
false")
+ }
+}
+
+func TestFfiOperatorListWithReturnType(t *testing.T) {
+ if ffiOperatorListWith.opts.rType != &typeResultList {
+ t.Fatalf("ffiOperatorListWith rType = %v, want typeResultList",
ffiOperatorListWith.opts.rType)
+ }
+}
+
+func TestFfiOperatorListWithArgTypes(t *testing.T) {
+ aTypes := ffiOperatorListWith.opts.aTypes
+ if len(aTypes) != 3 {
+ t.Fatalf("ffiOperatorListWith aTypes len = %d, want 3",
len(aTypes))
+ }
+ for i, at := range aTypes {
+ if at != &ffi.TypePointer {
+ t.Fatalf("ffiOperatorListWith aTypes[%d] = %v, want
TypePointer", i, at)
+ }
+ }
+}
diff --git a/bindings/go/tests/behavior_tests/list_test.go
b/bindings/go/tests/behavior_tests/list_test.go
index 29f0436ec..5fc09e27e 100644
--- a/bindings/go/tests/behavior_tests/list_test.go
+++ b/bindings/go/tests/behavior_tests/list_test.go
@@ -24,7 +24,7 @@ import (
"slices"
"strings"
- "github.com/apache/opendal/bindings/go"
+ opendal "github.com/apache/opendal/bindings/go"
"github.com/google/uuid"
"github.com/stretchr/testify/require"
)
@@ -33,7 +33,7 @@ func testsList(cap *opendal.Capability) []behaviorTest {
if !cap.Read() || !cap.Write() || !cap.List() || !cap.CreateDir() {
return nil
}
- return []behaviorTest{
+ tests := []behaviorTest{
testListCheck,
testListDir,
testListRichDir,
@@ -43,7 +43,12 @@ func testsList(cap *opendal.Capability) []behaviorTest {
testListNestedDir,
testListDirWithFilePath,
testListEntryMetadata,
+ testListWithDefaultOptions,
}
+ if cap.ListWithRecursive() {
+ tests = append(tests, testListWithRecursive)
+ }
+ return tests
}
func testListCheck(assert *require.Assertions, op *opendal.Operator, fixture
*fixture) {
@@ -289,3 +294,63 @@ func testListDirWithFilePath(assert *require.Assertions,
op *opendal.Operator, f
}
assert.Nil(obs.Error())
}
+
+func testListWithDefaultOptions(assert *require.Assertions, op
*opendal.Operator, fixture *fixture) {
+ parent := fixture.NewDirPath()
+ subDir := fixture.PushPath(fmt.Sprintf("%s%s/", parent,
uuid.NewString()))
+ fileInParent, content, _ := fixture.NewFileWithPath(fmt.Sprintf("%s%s",
parent, uuid.NewString()))
+ fileInSub := fixture.PushPath(fmt.Sprintf("%s%s", subDir,
uuid.NewString()))
+
+ assert.Nil(op.CreateDir(parent))
+ assert.Nil(op.CreateDir(subDir))
+ assert.Nil(op.Write(fileInParent, content))
+ assert.Nil(op.Write(fileInSub, content))
+
+ obs, err := op.List(parent)
+ assert.Nil(err)
+ defer obs.Close()
+
+ var paths []string
+ for obs.Next() {
+ paths = append(paths, obs.Entry().Path())
+ }
+ assert.Nil(obs.Error())
+
+ assert.NotContains(paths, fileInSub,
+ "List without options must not descend into sub-directories")
+ assert.Contains(paths, fileInParent, "direct child file must appear")
+ assert.Contains(paths, subDir, "direct child dir must appear")
+}
+
+func testListWithRecursive(assert *require.Assertions, op *opendal.Operator,
fixture *fixture) {
+ parent := fixture.NewDirPath()
+ subDir := fixture.PushPath(fmt.Sprintf("%s%s/", parent,
uuid.NewString()))
+ deepDir := fixture.PushPath(fmt.Sprintf("%s%s/", subDir,
uuid.NewString()))
+
+ fileTop := fixture.PushPath(fmt.Sprintf("%s%s", parent,
uuid.NewString()))
+ fileMid := fixture.PushPath(fmt.Sprintf("%s%s", subDir,
uuid.NewString()))
+ fileDeep := fixture.PushPath(fmt.Sprintf("%s%s", deepDir,
uuid.NewString()))
+
+ content := []byte("recursive test content")
+
+ assert.Nil(op.CreateDir(parent))
+ assert.Nil(op.CreateDir(subDir))
+ assert.Nil(op.CreateDir(deepDir))
+ assert.Nil(op.Write(fileTop, content))
+ assert.Nil(op.Write(fileMid, content))
+ assert.Nil(op.Write(fileDeep, content))
+
+ obs, err := op.List(parent, opendal.ListWithRecursive(true))
+ assert.Nil(err)
+ defer obs.Close()
+
+ var paths []string
+ for obs.Next() {
+ paths = append(paths, obs.Entry().Path())
+ }
+ assert.Nil(obs.Error())
+
+ assert.Contains(paths, fileTop, "recursive list must include top-level
file")
+ assert.Contains(paths, fileMid, "recursive list must include file in
sub-directory")
+ assert.Contains(paths, fileDeep, "recursive list must include file in
deep directory")
+}
diff --git a/bindings/go/types.go b/bindings/go/types.go
index ead55958d..3d0239f19 100644
--- a/bindings/go/types.go
+++ b/bindings/go/types.go
@@ -306,6 +306,8 @@ type opendalResultList struct {
type opendalLister struct{}
+type opendalListOptions struct{}
+
type opendalResultListerNext struct {
entry *opendalEntry
err *opendalError