Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
70 changes: 70 additions & 0 deletions bindings/c/include/opendal.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
/**
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There're a few list options in opendal core, I'm wondering why don't expose them all?
https://opendal.incubator.apache.org/docs/rust/opendal/options/struct.ListOptions.html

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would prefer to do each function in separate PR. WDYT?

* 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.
Expand Down Expand Up @@ -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`.
*
Expand Down Expand Up @@ -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
*
Expand Down
1 change: 1 addition & 0 deletions bindings/c/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
52 changes: 52 additions & 0 deletions bindings/c/src/operator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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`.
Expand Down
52 changes: 52 additions & 0 deletions bindings/c/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
9 changes: 9 additions & 0 deletions bindings/c/tests/test_framework.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
125 changes: 125 additions & 0 deletions bindings/c/tests/test_suites_list.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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() },
Expand All @@ -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 = {
Expand Down
Loading
Loading