diff --git a/.github/workflows/bindings_c.yml b/.github/workflows/bindings_c.yml index 7bcac37cc7b4..dccfcda03bad 100644 --- a/.github/workflows/bindings_c.yml +++ b/.github/workflows/bindings_c.yml @@ -64,6 +64,10 @@ jobs: - name: Check diff run: git diff --exit-code - - name: Build and Run BDD tests + - name: Build and Run tests working-directory: "bindings/c" run: make test + + - name: Build and Run memory-leak tests + working-directory: "bindings/c" + run: make memory_leak diff --git a/bindings/c/.gitignore b/bindings/c/.gitignore index 8f5beed0ec41..355533f6585f 100644 --- a/bindings/c/.gitignore +++ b/bindings/c/.gitignore @@ -2,3 +2,10 @@ build/ docs/ *.o + +examples/* +!examples/ +!examples/*.h +!examples/*.c +!examples/*.cc +!examples/*.cpp \ No newline at end of file diff --git a/bindings/c/Makefile b/bindings/c/Makefile index 0337f4d90c3e..e10f47bdf4b0 100644 --- a/bindings/c/Makefile +++ b/bindings/c/Makefile @@ -44,8 +44,16 @@ build: test: $(CXX) tests/bdd.cpp -o $(OBJ_DIR)/bdd $(CXXFLAGS) $(LDFLAGS) $(LIBS) $(CXX) tests/list.cpp -o $(OBJ_DIR)/list $(CXXFLAGS) $(LDFLAGS) $(LIBS) + $(CXX) tests/error_msg.cpp -o $(OBJ_DIR)/error_msg $(CXXFLAGS) $(LDFLAGS) $(LIBS) + $(OBJ_DIR)/bdd + $(OBJ_DIR)/list + $(OBJ_DIR)/error_msg + +.PHONY: test_memory_leak +memory_leak: $(VALGRIND) $(OBJ_DIR)/bdd $(VALGRIND) $(OBJ_DIR)/list + $(VALGRIND) $(OBJ_DIR)/error_msg .PHONY: doc doc: diff --git a/bindings/c/README.md b/bindings/c/README.md index 7d57c27fc22f..f89cead26666 100644 --- a/bindings/c/README.md +++ b/bindings/c/README.md @@ -24,13 +24,13 @@ int main() }; /* Write this into path "/testpath" */ - opendal_code code = opendal_operator_blocking_write(op, "/testpath", data); - assert(code == OPENDAL_OK); + opendal_error *error = opendal_operator_blocking_write(op, "/testpath", data); + assert(error == NULL); /* We can read it out, make sure the data is the same */ opendal_result_read r = opendal_operator_blocking_read(op, "/testpath"); opendal_bytes* read_bytes = r.data; - assert(r.code == OPENDAL_OK); + assert(r.error == NULL); assert(read_bytes->len == 24); /* Lets print it out */ diff --git a/bindings/c/examples/.gitignore b/bindings/c/examples/.gitignore deleted file mode 100644 index 887b0251a8fe..000000000000 --- a/bindings/c/examples/.gitignore +++ /dev/null @@ -1 +0,0 @@ -basicrw diff --git a/bindings/c/examples/basicrw.c b/bindings/c/examples/basic.c similarity index 83% rename from bindings/c/examples/basicrw.c rename to bindings/c/examples/basic.c index 3c3cd75cddf9..88071847c345 100644 --- a/bindings/c/examples/basicrw.c +++ b/bindings/c/examples/basic.c @@ -24,8 +24,11 @@ int main() { /* Initialize a operator for "memory" backend, with no options */ - const opendal_operator_ptr *op = opendal_operator_new("memory", 0); - assert(op->ptr != NULL); + opendal_result_operator_new result = opendal_operator_new("memory", 0); + assert(result.operator_ptr != NULL); + assert(result.error == NULL); + + opendal_operator_ptr* op = result.operator_ptr; /* Prepare some data to be written */ opendal_bytes data = { @@ -34,13 +37,13 @@ int main() }; /* Write this into path "/testpath" */ - opendal_code code = opendal_operator_blocking_write(op, "/testpath", data); - assert(code == OPENDAL_OK); + opendal_error* error = opendal_operator_blocking_write(op, "/testpath", data); + assert(error == NULL); /* We can read it out, make sure the data is the same */ opendal_result_read r = opendal_operator_blocking_read(op, "/testpath"); opendal_bytes* read_bytes = r.data; - assert(r.code == OPENDAL_OK); + assert(r.error == NULL); assert(read_bytes->len == 24); /* Lets print it out */ diff --git a/bindings/c/examples/error_handle.c b/bindings/c/examples/error_handle.c new file mode 100644 index 000000000000..3bb3308e7978 --- /dev/null +++ b/bindings/c/examples/error_handle.c @@ -0,0 +1,50 @@ +/* + * 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 "assert.h" +#include "opendal.h" +#include "stdio.h" + +// this example shows how to get error message from opendal_error +int main() +{ + /* Initialize a operator for "memory" backend, with no options */ + opendal_result_operator_new result = opendal_operator_new("memory", 0); + assert(result.operator_ptr != NULL); + assert(result.error == NULL); + + opendal_operator_ptr* op = result.operator_ptr; + + /* The read is supposed to fail */ + opendal_result_read r = opendal_operator_blocking_read(op, "/testpath"); + assert(r.error != NULL); + assert(r.error->code == OPENDAL_NOT_FOUND); + + /* Lets print the error message out */ + struct opendal_bytes* error_msg = &r.error->message; + for (int i = 0; i < error_msg->len; ++i) { + printf("%c", error_msg->data[i]); + } + + /* free the error since the error is not NULL */ + opendal_error_free(r.error); + + /* the operator_ptr is also heap allocated */ + opendal_operator_free(op); +} diff --git a/bindings/c/include/opendal.h b/bindings/c/include/opendal.h index 95c0be52d2a6..51d66aadb3c0 100644 --- a/bindings/c/include/opendal.h +++ b/bindings/c/include/opendal.h @@ -31,14 +31,6 @@ * added in the future. */ typedef enum opendal_code { - /** - * All is well - */ - OPENDAL_OK, - /** - * General error - */ - OPENDAL_ERROR, /** * returning it back. For example, s3 returns an internal service error. */ @@ -230,24 +222,6 @@ typedef struct opendal_operator_ptr { const struct BlockingOperator *ptr; } opendal_operator_ptr; -/** - * \brief The configuration for the initialization of opendal_operator_ptr. - * - * \note This is also a heap-allocated struct, please free it after you use it - * - * @see opendal_operator_new has an example of using opendal_operator_options - * @see opendal_operator_options_new This function construct the operator - * @see opendal_operator_options_free This function frees the heap memory of the operator - * @see opendal_operator_options_set This function allow you to set the options - */ -typedef struct opendal_operator_options { - /** - * The pointer to the Rust HashMap - * Only touch this on judging whether it is NULL. - */ - struct HashMap_String__String *inner; -} opendal_operator_options; - /** * \brief opendal_bytes carries raw-bytes with its length * @@ -268,13 +242,72 @@ typedef struct opendal_bytes { uintptr_t len; } opendal_bytes; +/** + * \brief The opendal error type for C binding, containing an error code and corresponding error + * message. + * + * The normal operations returns a pointer to the opendal_error, and the **nullptr normally + * represents no error has taken placed**. If any error has taken place, the caller should check + * the error code and print the error message. + * + * The error code is represented in opendal_code, which is a enum on different type of errors. + * The error messages is represented in opendal_bytes, which is a non-null terminated byte array. + * + * \note 1. The error message is on heap, so the error needs to be freed by the caller, by calling + * opendal_error_free. 2. The error message is not null terminated, so the caller should + * never use "%s" to print the error message. + * + * @see opendal_code + * @see opendal_bytes + * @see opendal_error_free + */ +typedef struct opendal_error { + enum opendal_code code; + struct opendal_bytes message; +} opendal_error; + +/** + * \brief The result type returned by opendal_operator_new() operation. + * + * If the init logic is successful, the `operator_ptr` field will be set to a valid + * pointer, and the `error` field will be set to null. If the init logic fails, the + * `operator_ptr` field will be set to null, and the `error` field will be set to a + * valid pointer with error code and error message. + * + * @see opendal_operator_new() + * @see opendal_operator_ptr + * @see opendal_error + */ +typedef struct opendal_result_operator_new { + struct opendal_operator_ptr *operator_ptr; + struct opendal_error *error; +} opendal_result_operator_new; + +/** + * \brief The configuration for the initialization of opendal_operator_ptr. + * + * \note This is also a heap-allocated struct, please free it after you use it + * + * @see opendal_operator_new has an example of using opendal_operator_options + * @see opendal_operator_options_new This function construct the operator + * @see opendal_operator_options_free This function frees the heap memory of the operator + * @see opendal_operator_options_set This function allow you to set the options + */ +typedef struct opendal_operator_options { + /** + * The pointer to the Rust HashMap + * Only touch this on judging whether it is NULL. + */ + struct HashMap_String__String *inner; +} opendal_operator_options; + /** * \brief The result type returned by opendal's read operation. * * The result type of read operation in opendal C binding, it contains - * the data that the read operation returns and a error code. + * the data that the read operation returns and an NULL error. * If the read operation failed, the `data` fields should be a nullptr - * and the error code is **NOT** OPENDAL_OK. + * and the error is not NULL. */ typedef struct opendal_result_read { /** @@ -282,17 +315,17 @@ typedef struct opendal_result_read { */ struct opendal_bytes *data; /** - * The error code, should be OPENDAL_OK if succeeds + * The error, if ok, it is null */ - enum opendal_code code; + struct opendal_error *error; } opendal_result_read; /** * \brief The result type returned by opendal_operator_is_exist(). * * The result type for opendal_operator_is_exist(), the field `is_exist` - * contains whether the path exists, and the field `code` contains the - * corresponding error code. + * contains whether the path exists, and the field `error` contains the + * corresponding error. If successful, the `error` field is null. * * \note If the opendal_operator_is_exist() fails, the `is_exist` field * will be set to false. @@ -303,9 +336,9 @@ typedef struct opendal_result_is_exist { */ bool is_exist; /** - * The error code, should be OPENDAL_OK if succeeds + * The error, if ok, it is null */ - enum opendal_code code; + struct opendal_error *error; } opendal_result_is_exist; /** @@ -331,7 +364,8 @@ typedef struct opendal_metadata { * \brief The result type returned by opendal_operator_stat(). * * The result type for opendal_operator_stat(), the field `meta` contains the metadata - * of the path, the field `code` represents whether the stat operation is successful. + * of the path, the field `error` represents whether the stat operation is successful. + * If successful, the `error` field is null. */ typedef struct opendal_result_stat { /** @@ -339,9 +373,9 @@ typedef struct opendal_result_stat { */ struct opendal_metadata *meta; /** - * The error code, should be OPENDAL_OK if succeeds + * The error, if ok, it is null */ - enum opendal_code code; + struct opendal_error *error; } opendal_result_stat; /** @@ -361,12 +395,18 @@ typedef struct 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. + * of the path, which is an iterator of the objects under the path. the field `error` represents + * whether the stat operation is successful. If successful, the `error` field is null. */ typedef struct opendal_result_list { + /** + * The lister, used for further listing operations + */ struct opendal_blocking_lister *lister; - enum opendal_code code; + /** + * The error, if ok, it is null + */ + struct opendal_error *error; } opendal_result_list; /** @@ -397,10 +437,9 @@ extern "C" { * @param options the pointer to the options for this operators, it could be NULL, which means no * option is set * @see opendal_operator_options - * @return A valid opendal_operator_ptr setup with the `scheme` and `options` is the construction - * succeeds. A null opendal_operator_ptr if any error happens. - * - * \remark You may use the `ptr` field of opendal_operator_ptr to check if it is NULL. + * @return A valid opendal_result_operator_new setup with the `scheme` and `options` is the construction + * succeeds. On success the operator_ptr field is a valid pointer to a newly allocated opendal_operator_ptr, + * and the error field is NULL. Otherwise, the operator_ptr field is a NULL pointer and the error field. * * # Example * @@ -412,7 +451,8 @@ extern "C" { * opendal_operator_options_set(options, "root", "/myroot"); * * // Construct the operator based on the options and scheme - * const opendal_operator_ptr *ptr = opendal_operator_new("memory", options); + * opendal_result_operator_new result = opendal_operator_new("memory", options); + * opendal_operator_ptr* op = result.operator_ptr; * * // you could free the options right away since the options is not used afterwards * opendal_operator_options_free(options); @@ -422,30 +462,27 @@ extern "C" { * * # Safety * - * It is **safe** under two cases below - * * The memory pointed to by `scheme` contain a valid nul terminator at the end of - * the string. - * * The `scheme` points to NULL, this function simply returns you a null opendal_operator_ptr. + * The only unsafe case is passing a invalid c string pointer to the `scheme` argument. */ -const struct opendal_operator_ptr *opendal_operator_new(const char *scheme, +struct opendal_result_operator_new opendal_operator_new(const char *scheme, const struct opendal_operator_options *options); /** * \brief Blockingly write raw bytes to `path`. * - * Write the `bytes` into the `path` blockingly by `op_ptr`, returns the opendal_code OPENDAL_OK - * if succeeds, others otherwise. + * Write the `bytes` into the `path` blockingly by `op_ptr`. + * Error is NULL if successful, otherwise it contains the error code and error message. * - * @NOTE It is important to notice that the `bytes` that is passes in will be consumed by this - * function. + * \note It is important to notice that the `bytes` that is passes in will be consumed by this + * function. Therefore, you should not use the `bytes` after this function returns. * * @param ptr The opendal_operator_ptr created previously * @param path The designated path you want to write your bytes in * @param bytes The opendal_byte typed bytes to be written * @see opendal_operator_ptr * @see opendal_bytes - * @see opendal_code - * @return OPENDAL_OK if succeeds others otherwise + * @see opendal_error + * @return NULL if succeeds, otherwise it contains the error code and error message. * * # Example * @@ -458,10 +495,10 @@ const struct opendal_operator_ptr *opendal_operator_new(const char *scheme, * opendal_bytes bytes = opendal_bytes { .data = (uint8_t*)data, .len = 13 }; * * // now you can write! - * opendal_code code = opendal_operator_blocking_write(ptr, "/testpath", bytes); + * opendal_error *err = opendal_operator_blocking_write(ptr, "/testpath", bytes); * * // Assert that this succeeds - * assert(code == OPENDAL_OK) + * assert(err == NULL); * ``` * * # Safety @@ -476,24 +513,23 @@ const struct opendal_operator_ptr *opendal_operator_new(const char *scheme, * * * If the `path` points to NULL, this function panics, i.e. exits with information */ -enum opendal_code opendal_operator_blocking_write(const struct opendal_operator_ptr *ptr, - const char *path, - struct opendal_bytes bytes); +struct opendal_error *opendal_operator_blocking_write(const struct opendal_operator_ptr *ptr, + const char *path, + struct opendal_bytes bytes); /** * \brief Blockingly read the data from `path`. * - * Read the data out from `path` blockingly by operator, returns - * an opendal_result_read with error code. + * Read the data out from `path` blockingly by operator. * * @param ptr The opendal_operator_ptr created previously * @param path The path you want to read the data out * @see opendal_operator_ptr * @see opendal_result_read - * @see opendal_code + * @see opendal_error * @return Returns opendal_result_read, the `data` field is a pointer to a newly allocated - * opendal_bytes, the `code` field contains the error code. If the `code` is not OPENDAL_OK, - * the `data` field points to NULL. + * opendal_bytes, the `error` field contains the error. If the `error` is not NULL, then + * the operation failed and the `data` field is a nullptr. * * \note If the read operation succeeds, the returned opendal_bytes is newly allocated on heap. * After your usage of that, please call opendal_bytes_free() to free the space. @@ -505,7 +541,7 @@ enum opendal_code opendal_operator_blocking_write(const struct opendal_operator_ * // ... you have write "Hello, World!" to path "/testpath" * * opendal_result_read r = opendal_operator_blocking_read(ptr, "testpath"); - * assert(r.code == OPENDAL_OK); + * assert(r.error == NULL); * * opendal_bytes *bytes = r.data; * assert(bytes->len == 13); @@ -527,14 +563,14 @@ struct opendal_result_read opendal_operator_blocking_read(const struct opendal_o /** * \brief Blockingly delete the object in `path`. * - * Delete the object in `path` blockingly by `op_ptr`, returns the opendal_code OPENDAL_OK - * if succeeds, others otherwise + * Delete the object in `path` blockingly by `op_ptr`. + * Error is NULL if successful, otherwise it contains the error code and error message. * * @param ptr The opendal_operator_ptr created previously * @param path The designated path you want to delete * @see opendal_operator_ptr - * @see opendal_code - * @return OPENDAL_OK if succeeds others otherwise + * @see opendal_error + * @return NULL if succeeds, otherwise it contains the error code and error message. * * # Example * @@ -545,13 +581,15 @@ struct opendal_result_read opendal_operator_blocking_read(const struct opendal_o * // prepare your data * char* data = "Hello, World!"; * opendal_bytes bytes = opendal_bytes { .data = (uint8_t*)data, .len = 13 }; - * opendal_code code = opendal_operator_blocking_write(ptr, "/testpath", bytes); + * opendal_error *error = opendal_operator_blocking_write(ptr, "/testpath", bytes); + * + * assert(error == NULL); * * // now you can delete! - * opendal_code code = opendal_operator_blocking_delete(ptr, "/testpath"); + * opendal_error *error = opendal_operator_blocking_delete(ptr, "/testpath"); * * // Assert that this succeeds - * assert(code == OPENDAL_OK) + * assert(error == NULL); * ``` * * # Safety @@ -564,37 +602,35 @@ struct opendal_result_read opendal_operator_blocking_read(const struct opendal_o * * * If the `path` points to NULL, this function panics, i.e. exits with information */ -enum opendal_code opendal_operator_blocking_delete(const struct opendal_operator_ptr *ptr, - const char *path); +struct opendal_error *opendal_operator_blocking_delete(const struct opendal_operator_ptr *ptr, + const char *path); /** * \brief Check whether the path exists. * * If the operation succeeds, no matter the path exists or not, - * the error code should be opendal_code::OPENDAL_OK. Otherwise, - * the field `is_exist` is filled with false, and the error code - * is set correspondingly. + * the error should be a nullptr. Otherwise, the field `is_exist` + * is filled with false, and the error is set * * @param ptr The opendal_operator_ptr created previously * @param path The path you want to check existence * @see opendal_operator_ptr * @see opendal_result_is_exist - * @see opendal_code + * @see opendal_error * @return Returns opendal_result_is_exist, the `is_exist` field contains whether the path exists. - * However, it the operation fails, the `is_exist` will contains false and the error code will be - * stored in the `code` field. + * However, it the operation fails, the `is_exist` will contains false and the error will be set. * * # Example * * ```C * // .. you previously wrote some data to path "/mytest/obj" * opendal_result_is_exist e = opendal_operator_is_exist(ptr, "/mytest/obj"); - * assert(e.code == OPENDAL_OK); + * assert(e.error == NULL); * assert(e.is_exist); * * // but you previously did **not** write any data to path "/yourtest/obj" - * opendal_result_is_exist e = opendal_operator_is_exist(ptr, "yourtest/obj"); - * assert(e.code == OPENDAL_OK); + * opendal_result_is_exist e = opendal_operator_is_exist(ptr, "/yourtest/obj"); + * assert(e.error == NULL); * assert(!e.is_exist); * ``` * @@ -614,26 +650,24 @@ struct opendal_result_is_exist opendal_operator_is_exist(const struct opendal_op /** * \brief Stat the path, return its metadata. * - * If the operation succeeds, the error code should be - * OPENDAL_OK. Otherwise, the field `meta` is filled with - * a NULL pointer, and the error code is set correspondingly. + * Error is NULL if successful, otherwise it contains the error code and error message. * * @param ptr The opendal_operator_ptr created previously * @param path The path you want to stat * @see opendal_operator_ptr * @see opendal_result_stat * @see opendal_metadata - * @return Returns opendal_result_stat, containing a metadata and a opendal_code. + * @return Returns opendal_result_stat, containing a metadata and an opendal_error. * If the operation succeeds, the `meta` field would holds a valid metadata and - * the `code` field should hold OPENDAL_OK. Otherwise the metadata will contain a - * NULL pointer, i.e. invalid, and the `code` will be set correspondingly. + * the `error` field should hold nullptr. Otherwise the metadata will contain a + * NULL pointer, i.e. invalid, and the `error` will be set correspondingly. * * # Example * * ```C * // ... previously you wrote "Hello, World!" to path "/testpath" * opendal_result_stat s = opendal_operator_stat(ptr, "/testpath"); - * assert(s.code == OPENDAL_OK); + * assert(s.error == NULL); * * const opendal_metadata *meta = s.meta; * @@ -664,7 +698,10 @@ struct opendal_result_stat opendal_operator_stat(const struct opendal_operator_p * @param ptr The opendal_operator_ptr created previously * @param path The designated path you want to delete * @see opendal_blocking_lister - * @return + * @return Returns opendal_result_list, containing a lister and an opendal_error. + * If the operation succeeds, the `lister` field would holds a valid lister and + * the `error` field should hold nullptr. Otherwise the `lister`` will contain a + * NULL pointer, i.e. invalid, and the `error` will be set correspondingly. * * # Example * @@ -673,7 +710,7 @@ struct opendal_result_stat opendal_operator_stat(const struct opendal_operator_p * // 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); + * assert(l.error == ERROR); * * opendal_blocking_lister *lister = l.lister; * opendal_list_entry *entry; @@ -704,6 +741,11 @@ struct opendal_result_stat opendal_operator_stat(const struct opendal_operator_p struct opendal_result_list opendal_operator_blocking_list(const struct opendal_operator_ptr *ptr, const char *path); +/** + * \brief Frees the opendal_error, ok to call on NULL + */ +void opendal_error_free(struct opendal_error *ptr); + /** * \brief Free the heap-allocated operator pointed by opendal_operator_ptr. * @@ -740,7 +782,7 @@ void opendal_metadata_free(struct opendal_metadata *ptr); * ```C * // ... previously you wrote "Hello, World!" to path "/testpath" * opendal_result_stat s = opendal_operator_stat(ptr, "/testpath"); - * assert(s.code == OPENDAL_OK); + * assert(s.error == NULL); * * opendal_metadata *meta = s.meta; * assert(opendal_metadata_content_length(meta) == 13); @@ -755,7 +797,7 @@ uint64_t opendal_metadata_content_length(const struct opendal_metadata *self); * ```C * // ... previously you wrote "Hello, World!" to path "/testpath" * opendal_result_stat s = opendal_operator_stat(ptr, "/testpath"); - * assert(s.code == OPENDAL_OK); + * assert(s.error == NULL); * * opendal_metadata *meta = s.meta; * assert(opendal_metadata_is_file(meta)); @@ -770,7 +812,7 @@ bool opendal_metadata_is_file(const struct opendal_metadata *self); * ```C * // ... previously you wrote "Hello, World!" to path "/testpath" * opendal_result_stat s = opendal_operator_stat(ptr, "/testpath"); - * assert(s.code == OPENDAL_OK); + * assert(s.error == NULL); * * opendal_metadata *meta = s.meta; * @@ -842,7 +884,7 @@ void opendal_lister_free(const struct opendal_blocking_lister *p); * * Path is relative to operator's root. Only valid in current operator. * - * @NOTE To free the string, you can directly call free() + * \note To free the string, you can directly call free() */ char *opendal_list_entry_path(const struct opendal_list_entry *self); @@ -853,7 +895,7 @@ char *opendal_list_entry_path(const struct opendal_list_entry *self); * If this entry is a dir, `Name` MUST endswith `/` * Otherwise, `Name` MUST NOT endswith `/`. * - * @NOTE To free the string, you can directly call free() + * \note To free the string, you can directly call free() */ char *opendal_list_entry_name(const struct opendal_list_entry *self); diff --git a/bindings/c/src/error.rs b/bindings/c/src/error.rs index 8aa8c87ca497..366d646ae0a4 100644 --- a/bindings/c/src/error.rs +++ b/bindings/c/src/error.rs @@ -17,20 +17,19 @@ use ::opendal as od; -/// The wrapper type for opendal's error, wrapped because of the -/// orphan rule -struct opendal_error(od::Error); +use crate::types::opendal_bytes; + +/// \brief The wrapper type for opendal's Rust core error, wrapped because of the +/// orphan rule. +/// +/// \note User should never use this type directly, use [`opendal_error`] instead. +struct raw_error(od::Error); /// \brief The error code for all opendal APIs in C binding. /// \todo The error handling is not complete, the error with error message will be /// added in the future. #[repr(C)] -pub enum opendal_code { - /// All is well - OPENDAL_OK, - /// General error - // \todo: make details in the `opendal_error *` - OPENDAL_ERROR, +pub(crate) enum opendal_code { /// returning it back. For example, s3 returns an internal service error. OPENDAL_UNEXPECTED, /// Underlying service doesn't support this operation. @@ -53,14 +52,7 @@ pub enum opendal_code { OPENDAL_IS_SAME_FILE, } -impl opendal_code { - pub(crate) fn from_opendal_error(e: od::Error) -> Self { - let error = opendal_error(e); - error.error_code() - } -} - -impl opendal_error { +impl raw_error { /// Convert the [`od::ErrorKind`] of [`od::Error`] to [`opendal_code`] pub(crate) fn error_code(&self) -> opendal_code { let e = &self.0; @@ -81,3 +73,60 @@ impl opendal_error { } } } + +/// \brief The opendal error type for C binding, containing an error code and corresponding error +/// message. +/// +/// The normal operations returns a pointer to the opendal_error, and the **nullptr normally +/// represents no error has taken placed**. If any error has taken place, the caller should check +/// the error code and print the error message. +/// +/// The error code is represented in opendal_code, which is a enum on different type of errors. +/// The error messages is represented in opendal_bytes, which is a non-null terminated byte array. +/// +/// \note 1. The error message is on heap, so the error needs to be freed by the caller, by calling +/// opendal_error_free. 2. The error message is not null terminated, so the caller should +/// never use "%s" to print the error message. +/// +/// @see opendal_code +/// @see opendal_bytes +/// @see opendal_error_free +#[repr(C)] +pub struct opendal_error { + code: opendal_code, + message: opendal_bytes, +} + +impl opendal_error { + // The caller should sink the error to heap memory and return the pointer + // that will not be freed by rustc + pub(crate) fn from_opendal_error(error: od::Error) -> Self { + let error = raw_error(error); + let code = error.error_code(); + let c_str = format!("{}", error.0); + let message = opendal_bytes::new(c_str.into_bytes()); + opendal_error { code, message } + } + + pub(crate) fn manual_error(code: opendal_code, message: String) -> Self { + let message = opendal_bytes::new(message.into_bytes()); + opendal_error { code, message } + } + + /// \brief Frees the opendal_error, ok to call on NULL + #[no_mangle] + pub unsafe extern "C" fn opendal_error_free(ptr: *mut opendal_error) { + if !ptr.is_null() { + let message_ptr = &(*ptr).message as *const opendal_bytes as *mut opendal_bytes; + if !message_ptr.is_null() { + let data_mut = unsafe { (*message_ptr).data as *mut u8 }; + let _ = unsafe { + Vec::from_raw_parts(data_mut, (*message_ptr).len, (*message_ptr).len) + }; + } + + // free the pointer + let _ = unsafe { Box::from_raw(ptr) }; + } + } +} diff --git a/bindings/c/src/lib.rs b/bindings/c/src/lib.rs index 6535b796573b..013e2a307359 100644 --- a/bindings/c/src/lib.rs +++ b/bindings/c/src/lib.rs @@ -34,10 +34,11 @@ use std::os::raw::c_char; use std::str::FromStr; use ::opendal as od; +use error::opendal_error; use result::opendal_result_list; +use result::opendal_result_operator_new; use types::opendal_blocking_lister; -use crate::error::opendal_code; use crate::result::opendal_result_is_exist; use crate::result::opendal_result_read; use crate::result::opendal_result_stat; @@ -57,10 +58,9 @@ use crate::types::opendal_operator_ptr; /// @param options the pointer to the options for this operators, it could be NULL, which means no /// option is set /// @see opendal_operator_options -/// @return A valid opendal_operator_ptr setup with the `scheme` and `options` is the construction -/// succeeds. A null opendal_operator_ptr if any error happens. -/// -/// \remark You may use the `ptr` field of opendal_operator_ptr to check if it is NULL. +/// @return A valid opendal_result_operator_new setup with the `scheme` and `options` is the construction +/// succeeds. On success the operator_ptr field is a valid pointer to a newly allocated opendal_operator_ptr, +/// and the error field is NULL. Otherwise, the operator_ptr field is a NULL pointer and the error field. /// /// # Example /// @@ -72,7 +72,8 @@ use crate::types::opendal_operator_ptr; /// opendal_operator_options_set(options, "root", "/myroot"); /// /// // Construct the operator based on the options and scheme -/// const opendal_operator_ptr *ptr = opendal_operator_new("memory", options); +/// opendal_result_operator_new result = opendal_operator_new("memory", options); +/// opendal_operator_ptr* op = result.operator_ptr; /// /// // you could free the options right away since the options is not used afterwards /// opendal_operator_options_free(options); @@ -82,24 +83,34 @@ use crate::types::opendal_operator_ptr; /// /// # Safety /// -/// It is **safe** under two cases below -/// * The memory pointed to by `scheme` contain a valid nul terminator at the end of -/// the string. -/// * The `scheme` points to NULL, this function simply returns you a null opendal_operator_ptr. +/// The only unsafe case is passing a invalid c string pointer to the `scheme` argument. #[no_mangle] pub unsafe extern "C" fn opendal_operator_new( scheme: *const c_char, options: *const opendal_operator_options, -) -> *const opendal_operator_ptr { +) -> opendal_result_operator_new { if scheme.is_null() { - return std::ptr::null(); + let error = opendal_error::manual_error( + error::opendal_code::OPENDAL_CONFIG_INVALID, + "The scheme given is pointing at NULL".into(), + ); + let result = opendal_result_operator_new { + operator_ptr: std::ptr::null_mut(), + error: Box::into_raw(Box::new(error)), + }; + return result; } let scheme_str = unsafe { std::ffi::CStr::from_ptr(scheme).to_str().unwrap() }; let scheme = match od::Scheme::from_str(scheme_str) { Ok(s) => s, - Err(_) => { - return std::ptr::null(); + Err(e) => { + let e = opendal_error::from_opendal_error(e); + let result = opendal_result_operator_new { + operator_ptr: std::ptr::null_mut(), + error: Box::into_raw(Box::new(e)), + }; + return result; } }; @@ -112,32 +123,39 @@ pub unsafe extern "C" fn opendal_operator_new( let op = match od::Operator::via_map(scheme, map) { Ok(o) => o.blocking(), - Err(_) => { - return std::ptr::null(); + Err(e) => { + let e = opendal_error::from_opendal_error(e); + let result = opendal_result_operator_new { + operator_ptr: std::ptr::null_mut(), + error: Box::into_raw(Box::new(e)), + }; + return result; } }; // this prevents the operator memory from being dropped by the Box let op = opendal_operator_ptr::from(Box::into_raw(Box::new(op))); - - Box::into_raw(Box::new(op)) + opendal_result_operator_new { + operator_ptr: Box::into_raw(Box::new(op)), + error: std::ptr::null_mut(), + } } /// \brief Blockingly write raw bytes to `path`. /// -/// Write the `bytes` into the `path` blockingly by `op_ptr`, returns the opendal_code OPENDAL_OK -/// if succeeds, others otherwise. +/// Write the `bytes` into the `path` blockingly by `op_ptr`. +/// Error is NULL if successful, otherwise it contains the error code and error message. /// -/// @NOTE It is important to notice that the `bytes` that is passes in will be consumed by this -/// function. +/// \note It is important to notice that the `bytes` that is passes in will be consumed by this +/// function. Therefore, you should not use the `bytes` after this function returns. /// /// @param ptr The opendal_operator_ptr created previously /// @param path The designated path you want to write your bytes in /// @param bytes The opendal_byte typed bytes to be written /// @see opendal_operator_ptr /// @see opendal_bytes -/// @see opendal_code -/// @return OPENDAL_OK if succeeds others otherwise +/// @see opendal_error +/// @return NULL if succeeds, otherwise it contains the error code and error message. /// /// # Example /// @@ -150,10 +168,10 @@ pub unsafe extern "C" fn opendal_operator_new( /// opendal_bytes bytes = opendal_bytes { .data = (uint8_t*)data, .len = 13 }; /// /// // now you can write! -/// opendal_code code = opendal_operator_blocking_write(ptr, "/testpath", bytes); +/// opendal_error *err = opendal_operator_blocking_write(ptr, "/testpath", bytes); /// /// // Assert that this succeeds -/// assert(code == OPENDAL_OK) +/// assert(err == NULL); /// ``` /// /// # Safety @@ -172,7 +190,7 @@ pub unsafe extern "C" fn opendal_operator_blocking_write( ptr: *const opendal_operator_ptr, path: *const c_char, bytes: opendal_bytes, -) -> opendal_code { +) -> *mut opendal_error { if path.is_null() { panic!("The path given is pointing at NULL"); } @@ -180,24 +198,26 @@ pub unsafe extern "C" fn opendal_operator_blocking_write( let op = (*ptr).as_ref(); let path = unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() }; match op.write(path, bytes) { - Ok(_) => opendal_code::OPENDAL_OK, - Err(e) => opendal_code::from_opendal_error(e), + Ok(_) => std::ptr::null_mut(), + Err(e) => { + let e = Box::new(opendal_error::from_opendal_error(e)); + Box::into_raw(e) + } } } /// \brief Blockingly read the data from `path`. /// -/// Read the data out from `path` blockingly by operator, returns -/// an opendal_result_read with error code. +/// Read the data out from `path` blockingly by operator. /// /// @param ptr The opendal_operator_ptr created previously /// @param path The path you want to read the data out /// @see opendal_operator_ptr /// @see opendal_result_read -/// @see opendal_code +/// @see opendal_error /// @return Returns opendal_result_read, the `data` field is a pointer to a newly allocated -/// opendal_bytes, the `code` field contains the error code. If the `code` is not OPENDAL_OK, -/// the `data` field points to NULL. +/// opendal_bytes, the `error` field contains the error. If the `error` is not NULL, then +/// the operation failed and the `data` field is a nullptr. /// /// \note If the read operation succeeds, the returned opendal_bytes is newly allocated on heap. /// After your usage of that, please call opendal_bytes_free() to free the space. @@ -209,7 +229,7 @@ pub unsafe extern "C" fn opendal_operator_blocking_write( /// // ... you have write "Hello, World!" to path "/testpath" /// /// opendal_result_read r = opendal_operator_blocking_read(ptr, "testpath"); -/// assert(r.code == OPENDAL_OK); +/// assert(r.error == NULL); /// /// opendal_bytes *bytes = r.data; /// assert(bytes->len == 13); @@ -241,26 +261,29 @@ pub unsafe extern "C" fn opendal_operator_blocking_read( let v = Box::new(opendal_bytes::new(d)); opendal_result_read { data: Box::into_raw(v), - code: opendal_code::OPENDAL_OK, + error: std::ptr::null_mut(), + } + } + Err(e) => { + let e = Box::new(opendal_error::from_opendal_error(e)); + opendal_result_read { + data: std::ptr::null_mut(), + error: Box::into_raw(e), } } - Err(e) => opendal_result_read { - data: std::ptr::null_mut(), - code: opendal_code::from_opendal_error(e), - }, } } /// \brief Blockingly delete the object in `path`. /// -/// Delete the object in `path` blockingly by `op_ptr`, returns the opendal_code OPENDAL_OK -/// if succeeds, others otherwise +/// Delete the object in `path` blockingly by `op_ptr`. +/// Error is NULL if successful, otherwise it contains the error code and error message. /// /// @param ptr The opendal_operator_ptr created previously /// @param path The designated path you want to delete /// @see opendal_operator_ptr -/// @see opendal_code -/// @return OPENDAL_OK if succeeds others otherwise +/// @see opendal_error +/// @return NULL if succeeds, otherwise it contains the error code and error message. /// /// # Example /// @@ -271,13 +294,15 @@ pub unsafe extern "C" fn opendal_operator_blocking_read( /// // prepare your data /// char* data = "Hello, World!"; /// opendal_bytes bytes = opendal_bytes { .data = (uint8_t*)data, .len = 13 }; -/// opendal_code code = opendal_operator_blocking_write(ptr, "/testpath", bytes); +/// opendal_error *error = opendal_operator_blocking_write(ptr, "/testpath", bytes); +/// +/// assert(error == NULL); /// /// // now you can delete! -/// opendal_code code = opendal_operator_blocking_delete(ptr, "/testpath"); +/// opendal_error *error = opendal_operator_blocking_delete(ptr, "/testpath"); /// /// // Assert that this succeeds -/// assert(code == OPENDAL_OK) +/// assert(error == NULL); /// ``` /// /// # Safety @@ -293,7 +318,7 @@ pub unsafe extern "C" fn opendal_operator_blocking_read( pub unsafe extern "C" fn opendal_operator_blocking_delete( ptr: *const opendal_operator_ptr, path: *const c_char, -) -> opendal_code { +) -> *mut opendal_error { if path.is_null() { panic!("The path given is pointing at NULL"); } @@ -301,38 +326,39 @@ pub unsafe extern "C" fn opendal_operator_blocking_delete( let op = (*ptr).as_ref(); let path = unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() }; match op.delete(path) { - Ok(_) => opendal_code::OPENDAL_OK, - Err(e) => opendal_code::from_opendal_error(e), + Ok(_) => std::ptr::null_mut(), + Err(e) => { + let e = Box::new(opendal_error::from_opendal_error(e)); + Box::into_raw(e) + } } } /// \brief Check whether the path exists. /// /// If the operation succeeds, no matter the path exists or not, -/// the error code should be opendal_code::OPENDAL_OK. Otherwise, -/// the field `is_exist` is filled with false, and the error code -/// is set correspondingly. +/// the error should be a nullptr. Otherwise, the field `is_exist` +/// is filled with false, and the error is set /// /// @param ptr The opendal_operator_ptr created previously /// @param path The path you want to check existence /// @see opendal_operator_ptr /// @see opendal_result_is_exist -/// @see opendal_code +/// @see opendal_error /// @return Returns opendal_result_is_exist, the `is_exist` field contains whether the path exists. -/// However, it the operation fails, the `is_exist` will contains false and the error code will be -/// stored in the `code` field. +/// However, it the operation fails, the `is_exist` will contains false and the error will be set. /// /// # Example /// /// ```C /// // .. you previously wrote some data to path "/mytest/obj" /// opendal_result_is_exist e = opendal_operator_is_exist(ptr, "/mytest/obj"); -/// assert(e.code == OPENDAL_OK); +/// assert(e.error == NULL); /// assert(e.is_exist); /// /// // but you previously did **not** write any data to path "/yourtest/obj" -/// opendal_result_is_exist e = opendal_operator_is_exist(ptr, "yourtest/obj"); -/// assert(e.code == OPENDAL_OK); +/// opendal_result_is_exist e = opendal_operator_is_exist(ptr, "/yourtest/obj"); +/// assert(e.error == NULL); /// assert(!e.is_exist); /// ``` /// @@ -359,37 +385,38 @@ pub unsafe extern "C" fn opendal_operator_is_exist( match op.is_exist(path) { Ok(e) => opendal_result_is_exist { is_exist: e, - code: opendal_code::OPENDAL_OK, - }, - Err(err) => opendal_result_is_exist { - is_exist: false, - code: opendal_code::from_opendal_error(err), + error: std::ptr::null_mut(), }, + Err(e) => { + let e = Box::new(opendal_error::from_opendal_error(e)); + opendal_result_is_exist { + is_exist: false, + error: Box::into_raw(e), + } + } } } /// \brief Stat the path, return its metadata. /// -/// If the operation succeeds, the error code should be -/// OPENDAL_OK. Otherwise, the field `meta` is filled with -/// a NULL pointer, and the error code is set correspondingly. +/// Error is NULL if successful, otherwise it contains the error code and error message. /// /// @param ptr The opendal_operator_ptr created previously /// @param path The path you want to stat /// @see opendal_operator_ptr /// @see opendal_result_stat /// @see opendal_metadata -/// @return Returns opendal_result_stat, containing a metadata and a opendal_code. +/// @return Returns opendal_result_stat, containing a metadata and an opendal_error. /// If the operation succeeds, the `meta` field would holds a valid metadata and -/// the `code` field should hold OPENDAL_OK. Otherwise the metadata will contain a -/// NULL pointer, i.e. invalid, and the `code` will be set correspondingly. +/// the `error` field should hold nullptr. Otherwise the metadata will contain a +/// NULL pointer, i.e. invalid, and the `error` will be set correspondingly. /// /// # Example /// /// ```C /// // ... previously you wrote "Hello, World!" to path "/testpath" /// opendal_result_stat s = opendal_operator_stat(ptr, "/testpath"); -/// assert(s.code == OPENDAL_OK); +/// assert(s.error == NULL); /// /// const opendal_metadata *meta = s.meta; /// @@ -420,12 +447,15 @@ pub unsafe extern "C" fn opendal_operator_stat( match op.stat(path) { Ok(m) => opendal_result_stat { meta: Box::into_raw(Box::new(opendal_metadata::new(m))), - code: opendal_code::OPENDAL_OK, - }, - Err(err) => opendal_result_stat { - meta: std::ptr::null_mut(), - code: opendal_code::from_opendal_error(err), + error: std::ptr::null_mut(), }, + Err(e) => { + let e = Box::new(opendal_error::from_opendal_error(e)); + opendal_result_stat { + meta: std::ptr::null_mut(), + error: Box::into_raw(e), + } + } } } @@ -438,7 +468,10 @@ pub unsafe extern "C" fn opendal_operator_stat( /// @param ptr The opendal_operator_ptr created previously /// @param path The designated path you want to delete /// @see opendal_blocking_lister -/// @return +/// @return Returns opendal_result_list, containing a lister and an opendal_error. +/// If the operation succeeds, the `lister` field would holds a valid lister and +/// the `error` field should hold nullptr. Otherwise the `lister`` will contain a +/// NULL pointer, i.e. invalid, and the `error` will be set correspondingly. /// /// # Example /// @@ -447,7 +480,7 @@ pub unsafe extern "C" fn opendal_operator_stat( /// // 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); +/// assert(l.error == ERROR); /// /// opendal_blocking_lister *lister = l.lister; /// opendal_list_entry *entry; @@ -488,12 +521,15 @@ pub unsafe extern "C" fn opendal_operator_blocking_list( match op.lister(path) { Ok(lister) => opendal_result_list { lister: Box::into_raw(Box::new(opendal_blocking_lister::new(lister))), - code: opendal_code::OPENDAL_OK, + error: std::ptr::null_mut(), }, - Err(e) => opendal_result_list { - lister: std::ptr::null_mut(), - code: opendal_code::from_opendal_error(e), - }, + Err(e) => { + let e = Box::new(opendal_error::from_opendal_error(e)); + opendal_result_list { + lister: std::ptr::null_mut(), + error: Box::into_raw(e), + } + } } } diff --git a/bindings/c/src/result.rs b/bindings/c/src/result.rs index c0d113718294..aa95c1190b01 100644 --- a/bindings/c/src/result.rs +++ b/bindings/c/src/result.rs @@ -20,30 +20,47 @@ //! "opendal_result_opendal_operator_ptr", which is unacceptable. Therefore, //! we are defining all Result types here -use crate::error::opendal_code; +use crate::error::opendal_error; use crate::types::opendal_blocking_lister; use crate::types::opendal_bytes; use crate::types::opendal_metadata; +use crate::types::opendal_operator_ptr; + +/// \brief The result type returned by opendal_operator_new() operation. +/// +/// If the init logic is successful, the `operator_ptr` field will be set to a valid +/// pointer, and the `error` field will be set to null. If the init logic fails, the +/// `operator_ptr` field will be set to null, and the `error` field will be set to a +/// valid pointer with error code and error message. +/// +/// @see opendal_operator_new() +/// @see opendal_operator_ptr +/// @see opendal_error +#[repr(C)] +pub struct opendal_result_operator_new { + pub operator_ptr: *mut opendal_operator_ptr, + pub error: *mut opendal_error, +} /// \brief The result type returned by opendal's read operation. /// /// The result type of read operation in opendal C binding, it contains -/// the data that the read operation returns and a error code. +/// the data that the read operation returns and an NULL error. /// If the read operation failed, the `data` fields should be a nullptr -/// and the error code is **NOT** OPENDAL_OK. +/// and the error is not NULL. #[repr(C)] pub struct opendal_result_read { /// The byte array with length returned by read operations pub data: *mut opendal_bytes, - /// The error code, should be OPENDAL_OK if succeeds - pub code: opendal_code, + /// The error, if ok, it is null + pub error: *mut opendal_error, } /// \brief The result type returned by opendal_operator_is_exist(). /// /// The result type for opendal_operator_is_exist(), the field `is_exist` -/// contains whether the path exists, and the field `code` contains the -/// corresponding error code. +/// contains whether the path exists, and the field `error` contains the +/// corresponding error. If successful, the `error` field is null. /// /// \note If the opendal_operator_is_exist() fails, the `is_exist` field /// will be set to false. @@ -51,29 +68,32 @@ pub struct opendal_result_read { pub struct opendal_result_is_exist { /// Whether the path exists pub is_exist: bool, - /// The error code, should be OPENDAL_OK if succeeds - pub code: opendal_code, + /// The error, if ok, it is null + pub error: *mut opendal_error, } /// \brief The result type returned by opendal_operator_stat(). /// /// The result type for opendal_operator_stat(), the field `meta` contains the metadata -/// of the path, the field `code` represents whether the stat operation is successful. +/// of the path, the field `error` represents whether the stat operation is successful. +/// If successful, the `error` field is null. #[repr(C)] pub struct opendal_result_stat { /// The metadata output of the stat pub meta: *mut opendal_metadata, - /// The error code, should be OPENDAL_OK if succeeds - pub code: opendal_code, + /// The error, if ok, it is null + pub error: *mut opendal_error, } /// \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. +/// of the path, which is an iterator of the objects under the path. the field `error` represents +/// whether the stat operation is successful. If successful, the `error` field is null. #[repr(C)] pub struct opendal_result_list { + /// The lister, used for further listing operations pub lister: *mut opendal_blocking_lister, - pub code: opendal_code, + /// The error, if ok, it is null + pub error: *mut opendal_error, } diff --git a/bindings/c/src/types.rs b/bindings/c/src/types.rs index 89d02826e85b..dbc0aa68ee9d 100644 --- a/bindings/c/src/types.rs +++ b/bindings/c/src/types.rs @@ -168,7 +168,7 @@ impl opendal_metadata { /// ```C /// // ... previously you wrote "Hello, World!" to path "/testpath" /// opendal_result_stat s = opendal_operator_stat(ptr, "/testpath"); - /// assert(s.code == OPENDAL_OK); + /// assert(s.error == NULL); /// /// opendal_metadata *meta = s.meta; /// assert(opendal_metadata_content_length(meta) == 13); @@ -186,7 +186,7 @@ impl opendal_metadata { /// ```C /// // ... previously you wrote "Hello, World!" to path "/testpath" /// opendal_result_stat s = opendal_operator_stat(ptr, "/testpath"); - /// assert(s.code == OPENDAL_OK); + /// assert(s.error == NULL); /// /// opendal_metadata *meta = s.meta; /// assert(opendal_metadata_is_file(meta)); @@ -206,7 +206,7 @@ impl opendal_metadata { /// ```C /// // ... previously you wrote "Hello, World!" to path "/testpath" /// opendal_result_stat s = opendal_operator_stat(ptr, "/testpath"); - /// assert(s.code == OPENDAL_OK); + /// assert(s.error == NULL); /// /// opendal_metadata *meta = s.meta; /// @@ -377,7 +377,7 @@ impl opendal_list_entry { /// /// Path is relative to operator's root. Only valid in current operator. /// - /// @NOTE To free the string, you can directly call free() + /// \note To free the string, you can directly call free() #[no_mangle] pub unsafe extern "C" fn opendal_list_entry_path(&self) -> *mut c_char { let s = (*self.inner).path(); @@ -391,7 +391,7 @@ impl opendal_list_entry { /// If this entry is a dir, `Name` MUST endswith `/` /// Otherwise, `Name` MUST NOT endswith `/`. /// - /// @NOTE To free the string, you can directly call free() + /// \note To free the string, you can directly call free() #[no_mangle] pub unsafe extern "C" fn opendal_list_entry_name(&self) -> *mut c_char { let s = (*self.inner).name(); diff --git a/bindings/c/tests/bdd.cpp b/bindings/c/tests/bdd.cpp index c7d79717292f..76d5047faf0a 100644 --- a/bindings/c/tests/bdd.cpp +++ b/bindings/c/tests/bdd.cpp @@ -41,8 +41,11 @@ class OpendalBddTest : public ::testing::Test { opendal_operator_options_set(options, "root", "/myroot"); // Given A new OpenDAL Blocking Operator - this->p = opendal_operator_new(scheme.c_str(), options); - EXPECT_TRUE(this->p->ptr); + opendal_result_operator_new result = opendal_operator_new(scheme.c_str(), options); + EXPECT_TRUE(result.error == nullptr); + + this->p = result.operator_ptr; + EXPECT_TRUE(this->p); opendal_operator_options_free(options); } @@ -58,17 +61,17 @@ TEST_F(OpendalBddTest, FeatureTest) .data = (uint8_t*)this->content.c_str(), .len = this->content.length(), }; - opendal_code code = opendal_operator_blocking_write(this->p, this->path.c_str(), data); - EXPECT_EQ(code, OPENDAL_OK); + opendal_error* error = opendal_operator_blocking_write(this->p, this->path.c_str(), data); + EXPECT_EQ(error, nullptr); // The blocking file "test" should exist opendal_result_is_exist e = opendal_operator_is_exist(this->p, this->path.c_str()); - EXPECT_EQ(e.code, OPENDAL_OK); + EXPECT_EQ(e.error, nullptr); EXPECT_TRUE(e.is_exist); // 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); + EXPECT_EQ(s.error, nullptr); opendal_metadata* meta = s.meta; EXPECT_TRUE(opendal_metadata_is_file(meta)); @@ -78,22 +81,22 @@ TEST_F(OpendalBddTest, FeatureTest) // The blocking file "test" must have content "Hello, World!" struct opendal_result_read r = opendal_operator_blocking_read(this->p, this->path.c_str()); - EXPECT_EQ(r.code, OPENDAL_OK); + EXPECT_EQ(r.error, nullptr); EXPECT_EQ(r.data->len, this->content.length()); for (int i = 0; i < r.data->len; i++) { EXPECT_EQ(this->content[i], (char)(r.data->data[i])); } // The blocking file should be deleted - code = opendal_operator_blocking_delete(this->p, this->path.c_str()); - EXPECT_EQ(code, OPENDAL_OK); + error = opendal_operator_blocking_delete(this->p, this->path.c_str()); + EXPECT_EQ(error, nullptr); e = opendal_operator_is_exist(this->p, this->path.c_str()); - EXPECT_EQ(e.code, OPENDAL_OK); + EXPECT_EQ(e.error, nullptr); EXPECT_FALSE(e.is_exist); // The deletion operation should be idempotent - code = opendal_operator_blocking_delete(this->p, this->path.c_str()); - EXPECT_EQ(code, OPENDAL_OK); + error = opendal_operator_blocking_delete(this->p, this->path.c_str()); + EXPECT_EQ(error, nullptr); opendal_bytes_free(r.data); } diff --git a/bindings/c/tests/error_msg.cpp b/bindings/c/tests/error_msg.cpp new file mode 100644 index 000000000000..3d06f4532dc5 --- /dev/null +++ b/bindings/c/tests/error_msg.cpp @@ -0,0 +1,72 @@ +/* + * 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 "gtest/gtest.h" +extern "C" { +#include "opendal.h" +} + +class OpendalErrorTest : 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"); + + opendal_result_operator_new result = opendal_operator_new("memory", options); + EXPECT_TRUE(result.error == nullptr); + + this->p = result.operator_ptr; + EXPECT_TRUE(this->p); + + opendal_operator_options_free(options); + } + + void TearDown() override { opendal_operator_free(this->p); } +}; + +// Test no memory leak of error message +TEST_F(OpendalErrorTest, ErrorReadTest) +{ + // Initialize a operator for "memory" backend, with no options + // The read is supposed to fail + opendal_result_read r = opendal_operator_blocking_read(this->p, "/testpath"); + ASSERT_NE(r.error, nullptr); + ASSERT_EQ(r.error->code, OPENDAL_NOT_FOUND); + + // Lets check the error message out + struct opendal_bytes* error_msg = &r.error->message; + ASSERT_NE(error_msg->data, nullptr); + ASSERT_GT(error_msg->len, 0); + + // the opendal_bytes read is heap allocated, please free it + opendal_bytes_free(r.data); + + // free the error + opendal_error_free(r.error); +} + +int main(int argc, char** argv) +{ + ::testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); +} diff --git a/bindings/c/tests/list.cpp b/bindings/c/tests/list.cpp index b6b44dd44d02..7b003e6a61fb 100644 --- a/bindings/c/tests/list.cpp +++ b/bindings/c/tests/list.cpp @@ -35,8 +35,11 @@ class OpendalListTest : public ::testing::Test { 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_result_operator_new result = opendal_operator_new("memory", options); + EXPECT_TRUE(result.error == nullptr); + + this->p = result.operator_ptr; + EXPECT_TRUE(this->p); opendal_operator_options_free(options); } @@ -63,11 +66,11 @@ TEST_F(OpendalListTest, ListDirTest) // write must succeed EXPECT_EQ(opendal_operator_blocking_write(this->p, path.c_str(), data), - OPENDAL_OK); + nullptr); // 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); + EXPECT_EQ(l.error, nullptr); opendal_blocking_lister* lister = l.lister; @@ -80,7 +83,7 @@ TEST_F(OpendalListTest, ListDirTest) // stat must succeed opendal_result_stat s = opendal_operator_stat(this->p, de_path); - EXPECT_EQ(s.code, OPENDAL_OK); + EXPECT_EQ(s.error, nullptr); if (!strcmp(de_path, path.c_str())) { found = true; @@ -102,7 +105,7 @@ TEST_F(OpendalListTest, ListDirTest) // delete EXPECT_EQ(opendal_operator_blocking_delete(this->p, path.c_str()), - OPENDAL_OK); + nullptr); opendal_lister_free(lister); } diff --git a/bindings/go/opendal.go b/bindings/go/opendal.go index 1494ab170837..4677a99dc675 100644 --- a/bindings/go/opendal.go +++ b/bindings/go/opendal.go @@ -49,9 +49,14 @@ func NewOperator(scheme string, opt Options) (*Operator, error) { for k, v := range opt { C.opendal_operator_options_set(opts, C.CString(k), C.CString(v)) } - op := C.opendal_operator_new(C.CString(scheme), opts) + ret := C.opendal_operator_new(C.CString(scheme), opts) + if ret.error != nil { + defer C.opendal_error_free(ret.error) + code, message := parseError(ret.error) + return nil, errors.New(fmt.Sprintf("create operator failed, error code: %d, error message: %s", code, message)) + } return &Operator{ - inner: op, + inner: ret.operator_ptr, }, nil } @@ -61,22 +66,36 @@ func (o *Operator) Write(key string, value []byte) error { } bytes := C.opendal_bytes{data: (*C.uchar)(unsafe.Pointer(&value[0])), len: C.ulong(len(value))} ret := C.opendal_operator_blocking_write(o.inner, C.CString(key), bytes) - if ret != 0 { - return errors.New(fmt.Sprintf("write failed, error code: %d", ret)) + if ret != nil { + defer C.opendal_error_free(ret) + code, message := parseError(ret) + return errors.New(fmt.Sprintf("write failed, error code: %d, error message: %s", code, message)) } return nil } func (o *Operator) Read(key string) ([]byte, error) { - result := C.opendal_operator_blocking_read(o.inner, C.CString(key)) - ret := int(result.code) - if ret != 0 { - return nil, errors.New(fmt.Sprintf("write failed, error code: %d", ret)) + ret := C.opendal_operator_blocking_read(o.inner, C.CString(key)) + if ret.error != nil { + defer C.opendal_error_free(ret.error) + code, message := parseError(ret.error) + return nil, errors.New(fmt.Sprintf("read failed, error code: %d, error message: %s", code, message)) } - return C.GoBytes(unsafe.Pointer(result.data.data), C.int(result.data.len)), nil + return C.GoBytes(unsafe.Pointer(ret.data.data), C.int(ret.data.len)), nil } func (o *Operator) Close() error { C.opendal_operator_free(o.inner) return nil } + +func decodeBytes(bs C.opendal_bytes) []byte { + return C.GoBytes(unsafe.Pointer(bs.data), C.int(bs.len)) +} + +func parseError(err *C.opendal_error) (int, string) { + code := int(err.code) + message := string(decodeBytes(err.message)) + + return code, message +} diff --git a/bindings/go/opendal_test.go b/bindings/go/opendal_test.go index 6723b67e2ce4..9ac76c273503 100644 --- a/bindings/go/opendal_test.go +++ b/bindings/go/opendal_test.go @@ -38,7 +38,7 @@ func TestOperations(t *testing.T) { assert.NoError(t, err) assert.Equal(t, "Hello World from OpenDAL CGO!", string(value)) - println(string(value)) + println(string(value)) } func BenchmarkOperator_Write(b *testing.B) { @@ -50,7 +50,7 @@ func BenchmarkOperator_Write(b *testing.B) { bs := bytes.Repeat([]byte("a"), 1024*1024) op.Write("test", bs) - b.SetBytes(1024*1024) + b.SetBytes(1024 * 1024) b.ResetTimer() for i := 0; i < b.N; i++ { op.Read("test") diff --git a/bindings/swift/.swift-format b/bindings/swift/.swift-format new file mode 100644 index 000000000000..d5d1e0158f7d --- /dev/null +++ b/bindings/swift/.swift-format @@ -0,0 +1,11 @@ +{ + "version": 1, + "lineLength": 100, + "indentation": { + "spaces": 4 + }, + "maximumBlankLines": 1, + "respectsExistingLineBreaks": true, + "lineBreakBeforeControlFlowKeywords": true, + "lineBreakBeforeEachArgument": true +} diff --git a/bindings/swift/OpenDAL/Package.swift b/bindings/swift/OpenDAL/Package.swift index d31439f32cca..f861d76979ba 100644 --- a/bindings/swift/OpenDAL/Package.swift +++ b/bindings/swift/OpenDAL/Package.swift @@ -27,7 +27,8 @@ let package = Package( products: [ .library( name: "OpenDAL", - targets: ["OpenDAL"]), + targets: ["OpenDAL"] + ) ], targets: [ .systemLibrary(name: "COpenDAL"), @@ -35,10 +36,12 @@ let package = Package( name: "OpenDAL", dependencies: ["COpenDAL"], linkerSettings: [ - .unsafeFlags(["-L\(packageRoot)/Sources/COpenDAL/lib"]), - ]), + .unsafeFlags(["-L\(packageRoot)/Sources/COpenDAL/lib"]) + ] + ), .testTarget( name: "OpenDALTests", - dependencies: ["OpenDAL"]), + dependencies: ["OpenDAL"] + ), ] ) diff --git a/bindings/swift/OpenDAL/Sources/OpenDAL/Data+OpenDAL.swift b/bindings/swift/OpenDAL/Sources/OpenDAL/Data+OpenDAL.swift index 92d0c827095c..8e6193375469 100644 --- a/bindings/swift/OpenDAL/Sources/OpenDAL/Data+OpenDAL.swift +++ b/bindings/swift/OpenDAL/Sources/OpenDAL/Data+OpenDAL.swift @@ -15,8 +15,8 @@ // specific language governing permissions and limitations // under the License. -import Foundation import COpenDAL +import Foundation extension Data { /// Creates a new data by managing `opendal_bytes` as its @@ -25,15 +25,15 @@ extension Data { /// This can be used to read data from Rust with zero-copying. /// The underlying buffer will be freed when the data gets /// deallocated. - init?(openDALBytes: UnsafeMutablePointer) { - guard let address = UnsafeRawPointer(openDALBytes.pointee.data) else { - return nil - } + init(openDALBytes: UnsafeMutablePointer) { + let address = UnsafeRawPointer(openDALBytes.pointee.data)! let length = Int(openDALBytes.pointee.len) - self.init(bytesNoCopy: .init(mutating: address), - count: length, - deallocator: .custom({ _, _ in - opendal_bytes_free(openDALBytes) - })) + self.init( + bytesNoCopy: .init(mutating: address), + count: length, + deallocator: .custom({ _, _ in + opendal_bytes_free(openDALBytes) + }) + ) } } diff --git a/bindings/swift/OpenDAL/Sources/OpenDAL/Operator.swift b/bindings/swift/OpenDAL/Sources/OpenDAL/Operator.swift index 1f2f91d1a4b7..6104c17c15d5 100644 --- a/bindings/swift/OpenDAL/Sources/OpenDAL/Operator.swift +++ b/bindings/swift/OpenDAL/Sources/OpenDAL/Operator.swift @@ -15,75 +15,84 @@ // specific language governing permissions and limitations // under the License. -import Foundation import COpenDAL +import Foundation -public enum OperatorError: Error { - case failedToBuild - case operationError(UInt32) +public struct OperatorError: Error { + let code: UInt32 + let message: Data } -/// A type used to access almost all OpenDAL APIs. public class Operator { var nativeOp: UnsafePointer - + deinit { opendal_operator_free(nativeOp) } - - /// Creates an operator with the given options. - /// - /// - Parameter options: The option map for creating the operator. - /// - Throws: `OperatorError` value that indicates an error if failed. - public init(scheme: String, options: [String : String] = [:]) throws { + + public init(scheme: String, options: [String: String] = [:]) throws { let nativeOptions = opendal_operator_options_new() defer { opendal_operator_options_free(nativeOptions) } - + for option in options { opendal_operator_options_set(nativeOptions, option.key, option.value) } - - guard let nativeOp = opendal_operator_new(scheme, nativeOptions) else { - throw OperatorError.failedToBuild - } - - guard nativeOp.pointee.ptr != nil else { - throw OperatorError.failedToBuild + + let ret = opendal_operator_new(scheme, nativeOptions) + if let err = ret.error { + defer { + opendal_error_free(err) + } + let immutableErr = err.pointee + let messagePointer = withUnsafePointer(to: immutableErr.message) { $0 } + let messageLength = Int(immutableErr.message.len) + throw OperatorError( + code: immutableErr.code.rawValue, + message: Data(bytes: messagePointer, count: messageLength) + ) } - self.nativeOp = nativeOp + self.nativeOp = UnsafePointer(ret.operator_ptr)! } - - /// Blockingly write the data to a given path. - /// - /// - Parameter data: The content to be written. - /// - Parameter path: The destination path for writing the data. - /// - Throws: `OperatorError` value that indicates an error if failed to write. + public func blockingWrite(_ data: Data, to path: String) throws { - let code = data.withUnsafeBytes { dataPointer in + let ret = data.withUnsafeBytes { dataPointer in let address = dataPointer.baseAddress!.assumingMemoryBound(to: UInt8.self) let bytes = opendal_bytes(data: address, len: UInt(dataPointer.count)) return opendal_operator_blocking_write(nativeOp, path, bytes) } - - guard code == OPENDAL_OK else { - throw OperatorError.operationError(code.rawValue) + + if let err = ret { + defer { + opendal_error_free(err) + } + let immutableErr = err.pointee + let messagePointer = withUnsafePointer(to: immutableErr.message) { $0 } + let messageLength = Int(immutableErr.message.len) + throw OperatorError( + code: immutableErr.code.rawValue, + message: Data(bytes: messagePointer, count: messageLength) + ) } } - - /// Blockingly read the data from a given path. - /// - /// - Parameter path: Path of the data to read. - /// - Returns: `NativeData` object if the data exists. - /// - Throws: `OperatorError` value that indicates an error if failed to read. - public func blockingRead(_ path: String) throws -> Data? { - let result = opendal_operator_blocking_read(nativeOp, path) - guard result.code == OPENDAL_OK else { - throw OperatorError.operationError(result.code.rawValue) + + public func blockingRead(_ path: String) throws -> Data { + let ret = opendal_operator_blocking_read(nativeOp, path) + if let err = ret.error { + defer { + opendal_error_free(err) + } + let immutableErr = err.pointee + let messagePointer = withUnsafePointer(to: immutableErr.message) { $0 } + let messageLength = Int(immutableErr.message.len) + throw OperatorError( + code: immutableErr.code.rawValue, + message: Data(bytes: messagePointer, count: messageLength) + ) } - - return .init(openDALBytes: result.data) + + return Data(openDALBytes: ret.data) } } diff --git a/bindings/swift/OpenDAL/Tests/OpenDALTests/OpenDALTests.swift b/bindings/swift/OpenDAL/Tests/OpenDALTests/OpenDALTests.swift index a776efdbdbb3..ef17c7887486 100644 --- a/bindings/swift/OpenDAL/Tests/OpenDALTests/OpenDALTests.swift +++ b/bindings/swift/OpenDAL/Tests/OpenDALTests/OpenDALTests.swift @@ -16,26 +16,27 @@ // under the License. import XCTest + @testable import OpenDAL final class OpenDALTests: XCTestCase { func testSimple() throws { - let op = try Operator(scheme: "memory", options: [ - "root": "/myroot" - ]) - + let op = try Operator( + scheme: "memory", + options: [ + "root": "/myroot" + ] + ) + let testContents = Data([1, 2, 3, 4]) try op.blockingWrite(testContents, to: "test") - - guard let readContents = try op.blockingRead("test") else { - XCTFail("Expected a `Data`") - return - } - + + let readContents = try op.blockingRead("test") + for (testByte, readByte) in zip(testContents, readContents) { XCTAssertEqual(testByte, readByte) } - + XCTAssertThrowsError(try op.blockingRead("test_not_exists")) } } diff --git a/bindings/zig/README.md b/bindings/zig/README.md index 7f25eb470fe0..315c686cb2bf 100644 --- a/bindings/zig/README.md +++ b/bindings/zig/README.md @@ -16,7 +16,7 @@ To compile OpenDAL from source code, you need: # build libopendal_c (underneath call make -C ../c) zig build libopendal_c # build and run unit tests -zig build test -fsummary +zig build test --summary all ``` ## License diff --git a/bindings/zig/src/opendal.zig b/bindings/zig/src/opendal.zig index 93c5047428b0..02bfc41e90c9 100644 --- a/bindings/zig/src/opendal.zig +++ b/bindings/zig/src/opendal.zig @@ -19,8 +19,6 @@ pub const c = @cImport(@cInclude("opendal.h")); // Zig code get values C code pub const Code = enum(c.opendal_code) { - OK = c.OPENDAL_OK, - ERROR = c.OPENDAL_ERROR, UNEXPECTED = c.OPENDAL_UNEXPECTED, UNSUPPORTED = c.OPENDAL_UNSUPPORTED, CONFIG_INVALID = c.OPENDAL_CONFIG_INVALID, @@ -58,7 +56,6 @@ pub fn codeToError(code: c.opendal_code) OpendalError!c.opendal_code { c.OPENDAL_ALREADY_EXISTS => error.AlreadyExists, c.OPENDAL_RATE_LIMITED => error.RateLimited, c.OPENDAL_IS_SAME_FILE => error.IsSameFile, - else => c.OPENDAL_ERROR, }; } pub fn errorToCode(err: OpendalError) c_int { diff --git a/bindings/zig/test/bdd.zig b/bindings/zig/test/bdd.zig index 79748fa3b196..c835e45b3400 100644 --- a/bindings/zig/test/bdd.zig +++ b/bindings/zig/test/bdd.zig @@ -40,7 +40,9 @@ test "Opendal BDD test" { opendal.c.opendal_operator_options_set(options, "root", "/myroot"); // Given A new OpenDAL Blocking Operator - self.p = opendal.c.opendal_operator_new(self.scheme, options); + var result = opendal.c.opendal_operator_new(self.scheme, options); + testing.expectEqual(result.@"error", null) catch unreachable; + self.p = result.operator_ptr; return self; } @@ -61,17 +63,17 @@ test "Opendal BDD test" { // c_str does not have len field (.* is ptr) .len = std.mem.len(testkit.content), }; - const code = opendal.c.opendal_operator_blocking_write(testkit.p, testkit.path, data); - try testing.expectEqual(code, @intFromEnum(Code.OK)); + const result = opendal.c.opendal_operator_blocking_write(testkit.p, testkit.path, data); + try testing.expectEqual(result, null); // The blocking file "test" should exist var e: opendal.c.opendal_result_is_exist = opendal.c.opendal_operator_is_exist(testkit.p, testkit.path); - try testing.expectEqual(e.code, @intFromEnum(Code.OK)); + try testing.expectEqual(e.@"error", null); try testing.expect(e.is_exist); // The blocking file "test" entry mode must be file var s: opendal.c.opendal_result_stat = opendal.c.opendal_operator_stat(testkit.p, testkit.path); - try testing.expectEqual(s.code, @intFromEnum(Code.OK)); + try testing.expectEqual(s.@"error", null); var meta: [*c]opendal.c.opendal_metadata = s.meta; try testing.expect(opendal.c.opendal_metadata_is_file(meta)); @@ -82,7 +84,7 @@ test "Opendal BDD test" { // The blocking file "test" must have content "Hello, World!" var r: opendal.c.opendal_result_read = opendal.c.opendal_operator_blocking_read(testkit.p, testkit.path); defer opendal.c.opendal_bytes_free(r.data); - try testing.expect(r.code == @intFromEnum(Code.OK)); + try testing.expect(r.@"error" == null); try testing.expectEqual(std.mem.len(testkit.content), r.data.*.len); var count: usize = 0;