diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 1a053704013e..939a55414dc9 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -138,6 +138,32 @@ jobs: name: python-docs path: ./bindings/python/docs + build-c-doc: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: Setup Rust toolchain + uses: ./.github/actions/setup + + - name: Setup doxygen + run: sudo apt-get install doxygen + + - name: Build C binding + working-directory: "bindings/c" + run: make build + + - name: Build Docs + working-directory: bindings/c + run: make doc + + - name: Upload docs + uses: actions/upload-artifact@v3 + with: + name: C-docs + path: ./bindings/c/docs/doxygen/html + build-website: runs-on: ubuntu-latest needs: @@ -145,6 +171,7 @@ jobs: - build-java-doc - build-nodejs-doc - build-python-doc + - build-c-doc steps: - uses: actions/checkout@v3 @@ -183,6 +210,12 @@ jobs: name: java-docs path: ./website/static/docs/java + - name: Download C docs + uses: actions/download-artifact@v3 + with: + name: C-docs + path: ./website/static/docs/c + - name: Install Dependencies working-directory: website run: yarn install --immutable diff --git a/bindings/c/CONTRIBUTING.md b/bindings/c/CONTRIBUTING.md index a71c7838eef7..1108e46bc555 100644 --- a/bindings/c/CONTRIBUTING.md +++ b/bindings/c/CONTRIBUTING.md @@ -78,3 +78,11 @@ make test [ PASSED ] 1 test. ``` +## Documentation +The documentation index page source is under `./docs/doxygen/html/index.html`. +If you want to build the documentations yourself, you could use +```sh +# this requires you to install doxygen +make doc +``` + diff --git a/bindings/c/Doxyfile b/bindings/c/Doxyfile new file mode 100644 index 000000000000..eb55736bc2a3 --- /dev/null +++ b/bindings/c/Doxyfile @@ -0,0 +1,291 @@ +# 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. + +PROJECT_NAME = "OpenDAL C Binding" +PROJECT_BRIEF = "The C binding for Apache OpenDAL" +OUTPUT_DIRECTORY = ./docs/doxygen +EXCLUDE_SYMBOLS = "HashMap_String__String" \ + "BlockingOperator" \ + "Metadata" +USE_MDFILE_AS_MAINPAGE = "README.md" + +DOXYFILE_ENCODING = UTF-8 +CREATE_SUBDIRS = NO +CREATE_SUBDIRS_LEVEL = 8 +ALLOW_UNICODE_NAMES = NO +OUTPUT_LANGUAGE = English +BRIEF_MEMBER_DESC = YES +REPEAT_BRIEF = YES +ABBREVIATE_BRIEF = "The $name class" \ + "The $name widget" \ + "The $name file" \ + is \ + provides \ + specifies \ + contains \ + represents \ + a \ + an \ + the +ALWAYS_DETAILED_SEC = NO +INLINE_INHERITED_MEMB = NO +FULL_PATH_NAMES = YES +SHORT_NAMES = NO +JAVADOC_AUTOBRIEF = NO +JAVADOC_BANNER = NO +QT_AUTOBRIEF = NO +MULTILINE_CPP_IS_BRIEF = NO +PYTHON_DOCSTRING = YES +INHERIT_DOCS = YES +SEPARATE_MEMBER_PAGES = NO +TAB_SIZE = 4 +OPTIMIZE_OUTPUT_FOR_C = NO +OPTIMIZE_OUTPUT_JAVA = NO +OPTIMIZE_FOR_FORTRAN = NO +OPTIMIZE_OUTPUT_VHDL = NO +OPTIMIZE_OUTPUT_SLICE = NO +MARKDOWN_SUPPORT = YES +TOC_INCLUDE_HEADINGS = 5 +AUTOLINK_SUPPORT = YES +BUILTIN_STL_SUPPORT = NO +CPP_CLI_SUPPORT = NO +SIP_SUPPORT = NO +IDL_PROPERTY_SUPPORT = YES +DISTRIBUTE_GROUP_DOC = NO +GROUP_NESTED_COMPOUNDS = NO +SUBGROUPING = YES +INLINE_GROUPED_CLASSES = NO +INLINE_SIMPLE_STRUCTS = NO +TYPEDEF_HIDES_STRUCT = NO +LOOKUP_CACHE_SIZE = 0 +NUM_PROC_THREADS = 1 +EXTRACT_ALL = ALL +EXTRACT_PRIVATE = NO +EXTRACT_PRIV_VIRTUAL = NO +EXTRACT_PACKAGE = NO +EXTRACT_STATIC = NO +EXTRACT_LOCAL_CLASSES = YES +EXTRACT_LOCAL_METHODS = NO +EXTRACT_ANON_NSPACES = NO +RESOLVE_UNNAMED_PARAMS = YES +HIDE_UNDOC_MEMBERS = NO +HIDE_UNDOC_CLASSES = NO +HIDE_FRIEND_COMPOUNDS = NO +HIDE_IN_BODY_DOCS = NO +INTERNAL_DOCS = NO +CASE_SENSE_NAMES = SYSTEM +HIDE_SCOPE_NAMES = NO +HIDE_COMPOUND_REFERENCE= NO +SHOW_HEADERFILE = YES +SHOW_INCLUDE_FILES = YES +SHOW_GROUPED_MEMB_INC = NO +FORCE_LOCAL_INCLUDES = NO +INLINE_INFO = YES +SORT_MEMBER_DOCS = YES +SORT_BRIEF_DOCS = NO +SORT_MEMBERS_CTORS_1ST = NO +SORT_GROUP_NAMES = NO +SORT_BY_SCOPE_NAME = NO +STRICT_PROTO_MATCHING = NO +GENERATE_TODOLIST = YES +GENERATE_TESTLIST = YES +GENERATE_BUGLIST = YES +GENERATE_DEPRECATEDLIST= YES +MAX_INITIALIZER_LINES = 30 +SHOW_USED_FILES = YES +SHOW_FILES = YES +SHOW_NAMESPACES = YES +QUIET = NO +WARNINGS = YES +WARN_IF_UNDOCUMENTED = YES +WARN_IF_DOC_ERROR = YES +WARN_IF_INCOMPLETE_DOC = YES +WARN_NO_PARAMDOC = NO +WARN_AS_ERROR = NO +WARN_FORMAT = "$file:$line: $text" +WARN_LINE_FORMAT = "at line $line of file $file" +INPUT = ./include ./README.md +INPUT_ENCODING = UTF-8 +FILE_PATTERNS = *.c \ + *.cc \ + *.cxx \ + *.cpp \ + *.c++ \ + *.java \ + *.ii \ + *.ixx \ + *.ipp \ + *.i++ \ + *.inl \ + *.idl \ + *.ddl \ + *.odl \ + *.h \ + *.hh \ + *.hxx \ + *.hpp \ + *.h++ \ + *.l \ + *.cs \ + *.d \ + *.php \ + *.php4 \ + *.php5 \ + *.phtml \ + *.inc \ + *.m \ + *.markdown \ + *.md \ + *.mm \ + *.dox \ + *.py \ + *.pyw \ + *.f90 \ + *.f95 \ + *.f03 \ + *.f08 \ + *.f18 \ + *.f \ + *.for \ + *.vhd \ + *.vhdl \ + *.ucf \ + *.qsf \ + *.ice +RECURSIVE = NO +EXCLUDE_SYMLINKS = NO +EXAMPLE_PATTERNS = * +EXAMPLE_RECURSIVE = NO +FILTER_SOURCE_FILES = NO +FORTRAN_COMMENT_AFTER = 72 +SOURCE_BROWSER = NO +INLINE_SOURCES = NO +STRIP_CODE_COMMENTS = YES +REFERENCED_BY_RELATION = NO +REFERENCES_RELATION = NO +REFERENCES_LINK_SOURCE = YES +SOURCE_TOOLTIPS = YES +USE_HTAGS = NO +VERBATIM_HEADERS = YES +ALPHABETICAL_INDEX = YES +GENERATE_HTML = YES +HTML_OUTPUT = html +HTML_FILE_EXTENSION = .html +HTML_COLORSTYLE = AUTO_LIGHT +HTML_COLORSTYLE_HUE = 220 +HTML_COLORSTYLE_SAT = 100 +HTML_COLORSTYLE_GAMMA = 80 +HTML_TIMESTAMP = NO +HTML_DYNAMIC_MENUS = YES +HTML_DYNAMIC_SECTIONS = NO +HTML_INDEX_NUM_ENTRIES = 100 +GENERATE_DOCSET = NO +DOCSET_FEEDNAME = "Doxygen generated docs" +DOCSET_BUNDLE_ID = org.doxygen.Project +DOCSET_PUBLISHER_ID = org.doxygen.Publisher +DOCSET_PUBLISHER_NAME = Publisher +GENERATE_HTMLHELP = NO +GENERATE_CHI = NO +BINARY_TOC = NO +TOC_EXPAND = NO +GENERATE_QHP = NO +QHP_NAMESPACE = org.doxygen.Project +QHP_VIRTUAL_FOLDER = doc +GENERATE_ECLIPSEHELP = NO +ECLIPSE_DOC_ID = org.doxygen.Project +DISABLE_INDEX = NO +GENERATE_TREEVIEW = NO +FULL_SIDEBAR = NO +ENUM_VALUES_PER_LINE = 4 +TREEVIEW_WIDTH = 250 +EXT_LINKS_IN_WINDOW = NO +OBFUSCATE_EMAILS = YES +HTML_FORMULA_FORMAT = png +FORMULA_FONTSIZE = 10 +USE_MATHJAX = NO +MATHJAX_VERSION = MathJax_2 +MATHJAX_FORMAT = HTML-CSS +SEARCHENGINE = YES +SERVER_BASED_SEARCH = NO +EXTERNAL_SEARCH = NO +SEARCHDATA_FILE = searchdata.xml +GENERATE_LATEX = NO +LATEX_OUTPUT = latex +MAKEINDEX_CMD_NAME = makeindex +LATEX_MAKEINDEX_CMD = makeindex +COMPACT_LATEX = NO +PAPER_TYPE = a4 +PDF_HYPERLINKS = YES +USE_PDFLATEX = YES +LATEX_BATCHMODE = NO +LATEX_HIDE_INDICES = NO +LATEX_BIB_STYLE = plain +LATEX_TIMESTAMP = NO +GENERATE_RTF = NO +RTF_OUTPUT = rtf +COMPACT_RTF = NO +RTF_HYPERLINKS = NO +GENERATE_MAN = NO +MAN_OUTPUT = man +MAN_EXTENSION = .3 +MAN_LINKS = NO +GENERATE_XML = NO +XML_OUTPUT = xml +XML_PROGRAMLISTING = YES +XML_NS_MEMB_FILE_SCOPE = NO +GENERATE_DOCBOOK = NO +DOCBOOK_OUTPUT = docbook +GENERATE_AUTOGEN_DEF = NO +GENERATE_PERLMOD = NO +PERLMOD_LATEX = NO +PERLMOD_PRETTY = YES +ENABLE_PREPROCESSING = YES +MACRO_EXPANSION = NO +EXPAND_ONLY_PREDEF = NO +SEARCH_INCLUDES = YES +SKIP_FUNCTION_MACROS = YES +ALLEXTERNALS = NO +EXTERNAL_GROUPS = YES +EXTERNAL_PAGES = YES +HIDE_UNDOC_RELATIONS = YES +HAVE_DOT = NO +DOT_NUM_THREADS = 0 +DOT_COMMON_ATTR = "fontname=Helvetica,fontsize=10" +DOT_EDGE_ATTR = "labelfontname=Helvetica,labelfontsize=10" +DOT_NODE_ATTR = "shape=box,height=0.2,width=0.4" +CLASS_GRAPH = YES +COLLABORATION_GRAPH = YES +GROUP_GRAPHS = YES +UML_LOOK = NO +UML_LIMIT_NUM_FIELDS = 10 +DOT_UML_DETAILS = NO +DOT_WRAP_THRESHOLD = 17 +TEMPLATE_RELATIONS = NO +INCLUDE_GRAPH = YES +INCLUDED_BY_GRAPH = YES +CALL_GRAPH = NO +CALLER_GRAPH = NO +GRAPHICAL_HIERARCHY = YES +DIRECTORY_GRAPH = YES +DIR_GRAPH_MAX_DEPTH = 1 +DOT_IMAGE_FORMAT = png +INTERACTIVE_SVG = NO +DOT_GRAPH_MAX_NODES = 50 +MAX_DOT_GRAPH_DEPTH = 0 +DOT_MULTI_TARGETS = NO +GENERATE_LEGEND = YES +DOT_CLEANUP = YES diff --git a/bindings/c/Makefile b/bindings/c/Makefile index 7455cd7b9660..36b1547adba9 100644 --- a/bindings/c/Makefile +++ b/bindings/c/Makefile @@ -16,10 +16,13 @@ # under the License. RPATH=$(PWD)/../../target/debug +OBJ_DIR=./build +DOC_DIR=./docs + CXXFLAGS=-I./include -std=c++14 LDFLAGS=-L$(RPATH) -Wl,-rpath,$(RPATH) + LIBS=-lopendal_c -lgtest -lpthread -OBJ_DIR=./build .PHONY: all all: build test @@ -38,7 +41,14 @@ test: $(CXX) tests/bdd.cpp -o $(OBJ_DIR)/bdd $(CXXFLAGS) $(LDFLAGS) $(LIBS) $(OBJ_DIR)/bdd +.PHONY: doc +doc: + mkdir -p $(DOC_DIR) + doxygen Doxyfile + .PHONY: clean clean: cargo clean rm -rf $(OBJ_DIR) + rm -rf $(DOC_DIR) + diff --git a/bindings/c/README.md b/bindings/c/README.md index 7779077f225f..ec340336cde5 100644 --- a/bindings/c/README.md +++ b/bindings/c/README.md @@ -2,17 +2,63 @@ # Build C bindings -To compile OpenDAL from source code, you'll need: -- C/C++ Toolchain: provides basic tools for compiling and linking OpenDAL C binding. This is available as `build-essential` on Ubuntu and similar names on other platforms. -- `clang-format`: OpenDAL uses clang-format to keep C binding code base clean. The `opendal.h` should be included and formatted by users manually. +To build OpenDAL C binding, the following is all you need: +- **A C++ compiler** that supports **c++14**, *e.g.* clang++ and g++ -```bash -make -make format +- To format the code, you need to install **clang-format** + - The `opendal.h` is not formatted by hands when you contribute, please do not format the file. **Use `make format` only.** + - If your contribution is related to the files under `./tests`, you may format it before submitting your pull request. But notice that different versions of `clang-format` may format the files differently. + +- **GTest(Google Test)** need to be installed to build the BDD (Behavior Driven Development) tests. To see how to build, check [here](https://github.com/google/googletest). +- (optional) **Doxygen** need to be installed to generate documentations. + +For Ubuntu and Debian: +```shell +# install C/C++ toolchain +sudo apt install -y build-essential + +# install clang-format +sudo apt install clang-format + +# install and build GTest library under /usr/lib and softlink to /usr/local/lib +sudo apt-get install libgtest-dev +cd /usr/src/gtest +sudo cmake CMakeLists.txt +sudo make +sudo cp lib/*.a /usr/lib +sudo ln -s /usr/lib/libgtest.a /usr/local/lib/libgtest.a +sudo ln -s /usr/lib/libgtest_main.a /usr/local/lib/libgtest_main.a +``` + +## Build +To build the library and header file. +```shell make build +``` + +- The header file `opendal.h` is under `./include` +- The library is under `../../target/debug` after building. + +To clean the build results. +```shell +make clean +``` + +## Test +To build and run the tests. (Note that you need to install GTest) +```shell make test ``` +## Documentation +The documentation index page source is under `./docs/doxygen/html/index.html`. +If you want to build the documentations yourself, you could use +```sh +# this requires you to install doxygen +make doc +``` + + ## License [Apache v2.0](https://www.apache.org/licenses/LICENSE-2.0) diff --git a/bindings/c/cbindgen.toml b/bindings/c/cbindgen.toml index 82db7cc876b2..aafbb6d5f107 100644 --- a/bindings/c/cbindgen.toml +++ b/bindings/c/cbindgen.toml @@ -19,9 +19,9 @@ # for detailed documentation of every option here. cpp_compat = true -documentation_style = "c" +documentation_style = "doxy" header = """ -/** +/* * 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 diff --git a/bindings/c/include/opendal.h b/bindings/c/include/opendal.h index 45bc2ee9491a..04a809a9565c 100644 --- a/bindings/c/include/opendal.h +++ b/bindings/c/include/opendal.h @@ -1,4 +1,4 @@ -/** +/* * 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 @@ -25,172 +25,239 @@ #include #include -/* - The error code for opendal APIs in C binding +/** + * \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. */ typedef enum opendal_code { - /* - All is well + /** + * All is well */ OPENDAL_OK, - /* - General error + /** + * General error */ OPENDAL_ERROR, - /* - returning it back. For example, s3 returns an internal service error. + /** + * returning it back. For example, s3 returns an internal service error. */ OPENDAL_UNEXPECTED, - /* - Underlying service doesn't support this operation. + /** + * Underlying service doesn't support this operation. */ OPENDAL_UNSUPPORTED, - /* - The config for backend is invalid. + /** + * The config for backend is invalid. */ OPENDAL_CONFIG_INVALID, - /* - The given path is not found. + /** + * The given path is not found. */ OPENDAL_NOT_FOUND, - /* - The given path doesn't have enough permission for this operation + /** + * The given path doesn't have enough permission for this operation */ OPENDAL_PERMISSION_DENIED, - /* - The given path is a directory. + /** + * The given path is a directory. */ OPENDAL_IS_A_DIRECTORY, - /* - The given path is not a directory. + /** + * The given path is not a directory. */ OPENDAL_NOT_A_DIRECTORY, - /* - The given path already exists thus we failed to the specified operation on it. + /** + * The given path already exists thus we failed to the specified operation on it. */ OPENDAL_ALREADY_EXISTS, - /* - Requests that sent to this path is over the limit, please slow down. + /** + * Requests that sent to this path is over the limit, please slow down. */ OPENDAL_RATE_LIMITED, - /* - The given file paths are same. + /** + * The given file paths are same. */ OPENDAL_IS_SAME_FILE, } opendal_code; -/* - BlockingOperator is the entry for all public blocking APIs. - - Read [`concepts`][docs::concepts] for know more about [`Operator`]. - - # Examples - - Read more backend init examples in [`services`] - - ``` - # use anyhow::Result; - use opendal::services::Fs; - use opendal::BlockingOperator; - use opendal::Operator; - #[tokio::main] - async fn main() -> Result<()> { - // Create fs backend builder. - let mut builder = Fs::default(); - // Set the root for fs, all operations will happen under this root. - // - // NOTE: the root must be absolute path. - builder.root("/tmp"); - - // Build an `BlockingOperator` to start operating the storage. - let _: BlockingOperator = Operator::new(builder)?.finish().blocking(); - - Ok(()) - } - ``` +/** + * BlockingOperator is the entry for all public blocking APIs. + * + * Read [`concepts`][docs::concepts] for know more about [`Operator`]. + * + * # Examples + * + * Read more backend init examples in [`services`] + * + * ``` + * # use anyhow::Result; + * use opendal::services::Fs; + * use opendal::BlockingOperator; + * use opendal::Operator; + * #[tokio::main] + * async fn main() -> Result<()> { + * // Create fs backend builder. + * let mut builder = Fs::default(); + * // Set the root for fs, all operations will happen under this root. + * // + * // NOTE: the root must be absolute path. + * builder.root("/tmp"); + * + * // Build an `BlockingOperator` to start operating the storage. + * let _: BlockingOperator = Operator::new(builder)?.finish().blocking(); + * + * Ok(()) + * } + * ``` */ typedef struct BlockingOperator BlockingOperator; typedef struct HashMap_String__String HashMap_String__String; -/* - Metadata carries all metadata associated with a path. - - # Notes - - mode and content_length are required metadata that all services - should provide during `stat` operation. But in `list` operation, - a.k.a., `Entry`'s content length could be `None`. +/** + * Metadata carries all metadata associated with a path. + * + * # Notes + * + * mode and content_length are required metadata that all services + * should provide during `stat` operation. But in `list` operation, + * a.k.a., `Entry`'s content length could be `None`. */ typedef struct Metadata Metadata; -/* - The [`opendal_operator_ptr`] owns a pointer to a [`od::BlockingOperator`]. - It is also the key struct that OpenDAL's APIs access the real - operator's memory. The use of OperatorPtr is zero cost, it - only returns a reference of the underlying Operator. - - The [`opendal_operator_ptr`] also has a transparent layout, allowing you - to check its validity by native boolean operator. - e.g. you could check by (!ptr) on a [`opendal_operator_ptr`] +/** + * \brief Used to access almost all OpenDAL APIs. It represents a + * operator that provides the unified interfaces provided by OpenDAL. + * + * @see opendal_operator_new This function construct the operator + * @see opendal_operator_free This function frees the heap memory of the operator + * + * \note The opendal_operator_ptr actually owns a pointer to + * a opendal::BlockingOperator, which is inside the Rust core code. + * + * \remark You may use the field `ptr` to check whether this is a NULL + * operator. */ -typedef const struct BlockingOperator *opendal_operator_ptr; +typedef struct opendal_operator_ptr { + /** + * The pointer to the opendal::BlockingOperator in the Rust code. + * Only touch this on judging whether it is NULL. + */ + const struct BlockingOperator *ptr; +} opendal_operator_ptr; -/* - [`opendal_operator_options`] represents a series of string type key-value pairs, it may be used for initialization +/** + * \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 HashMap_String__String *opendal_operator_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; -/* - The [`opendal_bytes`] type is a C-compatible substitute for [`Vec`] - in Rust, it will not be deallocated automatically like what - has been done in Rust. Instead, you have to call [`opendal_free_bytes`] - to free the heap memory to avoid memory leak. +/** + * \brief opendal_bytes carries raw-bytes with its length + * + * The opendal_bytes type is a C-compatible substitute for Vec type + * in Rust, it has to be manually freed. You have to call opendal_bytes_free() + * to free the heap memory to avoid memory leak. + * + * @see opendal_bytes_free */ typedef struct opendal_bytes { + /** + * Pointing to the byte array on heap + */ const uint8_t *data; + /** + * The length of the byte array + */ uintptr_t len; } opendal_bytes; -/* - The Rust-like Result type of opendal C binding, it contains - the data that the read operation returns and a error code - If the read operation failed, the `data` fields should be a nullptr - and the error code is NOT OPENDAL_OK. +/** + * \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. + * If the read operation failed, the `data` fields should be a nullptr + * and the error code is **NOT** OPENDAL_OK. */ typedef struct opendal_result_read { + /** + * The byte array with length returned by read operations + */ struct opendal_bytes *data; + /** + * The error code, should be OPENDAL_OK if succeeds + */ enum opendal_code code; } opendal_result_read; -/* - 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. +/** + * \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. + * + * \note If the opendal_operator_is_exist() fails, the `is_exist` field + * will be set to false. */ typedef struct opendal_result_is_exist { + /** + * Whether the path exists + */ bool is_exist; + /** + * The error code, should be OPENDAL_OK if succeeds + */ enum opendal_code code; } opendal_result_is_exist; -/* - Metadata carries all metadata associated with an path. - - # Notes - - mode and content_length are required metadata that all services - should provide during `stat` operation. But in `list` operation, - a.k.a., `Entry`'s content length could be NULL. +/** + * \brief Carries all metadata associated with a path. + * + * The metadata of the "thing" under a path. Please **only** use the opendal_metadata + * with our provided API, e.g. opendal_metadata_content_length(). + * + * \note The metadata is also heap-allocated, please call opendal_metadata_free() on this + * to free the heap memory. + * + * @see opendal_metadata_free */ -typedef const struct Metadata *opendal_metadata; +typedef struct opendal_metadata { + /** + * The pointer to the opendal::Metadata in the Rust code. + * Only touch this on judging whether it is NULL. + */ + const struct Metadata *inner; +} opendal_metadata; -/* - The result type for [`opendal_operator_stat()`], the meta contains the metadata - of the path, the code represents whether the stat operation is successful. Note - that the operation could be successful even if the path does not exist. +/** + * \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. */ typedef struct opendal_result_stat { - opendal_metadata meta; + /** + * The metadata output of the stat + */ + struct opendal_metadata meta; + /** + * The error code, should be OPENDAL_OK if succeeds + */ enum opendal_code code; } opendal_result_stat; @@ -198,177 +265,344 @@ typedef struct opendal_result_stat { extern "C" { #endif // __cplusplus -/* - Uses an array of key-value pairs to initialize the operator based on provided scheme. - - # Example - - Following is a C example. - ```no_run - opendal_operator_options options = opendal_operator_options_new(); - opendal_operator_options_set(&options, "root", "/myroot"); - - opendal_operator_ptr ptr = opendal_operator_new("memory", options); - opendal_operator_options_free(&options); - - // ... your operations - ``` - - # Safety - - This function is unsafe because it deferences and casts the raw pointers. - - It is [safe] under two cases below - * The memory pointed to by `scheme` must 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 - - # Returns - - Returns a result type [`opendal_result_op`], with operator_ptr. If the construction succeeds - the error is nullptr, otherwise it contains the error information. +/** + * \brief Construct an operator based on `scheme` and `options` + * + * Uses an array of key-value pairs to initialize the operator based on provided `scheme` + * and `options`. For each scheme, i.e. Backend, different options could be set, you may + * reference the [documentation](https://opendal.apache.org/docs/category/services/) for + * each service, especially for the **Configuration Part**. + * + * @param scheme the service scheme you want to specify, e.g. "fs", "s3", "supabase" + * @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. + * + * # Example + * + * Following is an example. + * ```C + * // Allocate a new options + * opendal_operator_options options = opendal_operator_options_new(); + * // Set the options you need + * opendal_operator_options_set(&options, "root", "/myroot"); + * + * // Construct the operator based on the options and scheme + * opendal_operator_ptr ptr = opendal_operator_new("memory", options); + * + * // you could free the options right away since the options is not used afterwards + * opendal_operator_options_free(&options); + * + * // ... your operations + * ``` + * + * # 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. */ -opendal_operator_ptr opendal_operator_new(const char *scheme, opendal_operator_options options); +struct opendal_operator_ptr opendal_operator_new(const char *scheme, + const struct opendal_operator_options *options); -/* - Write the data into the path blockingly by operator, returns the error code OPENDAL_OK - if succeeds, others otherwise - - # Safety - - It is [safe] under the cases below - * The memory pointed to by `path` must contain a valid nul terminator at the end of - the string. - - # Panic - - * If the `path` points to NULL, this function panics +/** + * \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 + * + * @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 + * + * # Example + * + * Following is an example + * ```C + * //...prepare your opendal_operator_ptr, named ptr for example + * + * // prepare your data + * char* data = "Hello, World!"; + * 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); + * + * // Assert that this succeeds + * assert(code == OPENDAL_OK) + * ``` + * + * # Safety + * + * It is **safe** under the cases below + * * The memory pointed to by `path` must contain a valid nul terminator at the end of + * the string. + * * The `bytes` provided has valid byte in the `data` field and the `len` field is set + * correctly. + * + * # Panic + * + * * If the `path` points to NULL, this function panics, i.e. exits with information */ -enum opendal_code opendal_operator_blocking_write(opendal_operator_ptr op_ptr, +enum opendal_code opendal_operator_blocking_write(struct opendal_operator_ptr ptr, const char *path, struct opendal_bytes bytes); -/* - Read the data out from path into a [`Bytes`] blockingly by operator, returns - a result with error code. If the error code is not OPENDAL_OK, the `data` field - of the result points to NULL. - - # Safety - - It is [safe] under the cases below - * The memory pointed to by `path` must contain a valid nul terminator at the end of - the string. - - # Panic - - * If the `path` points to NULL, this function panics +/** + * \brief Blockingly read the data from `path`. + * + * Read the data out from `path` blockingly by operator, returns + * an opendal_result_read with error code. + * + * @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 + * @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. + * + * \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. + * + * # Example + * + * Following is an example + * ```C + * // ... you have write "Hello, World!" to path "/testpath" + * + * opendal_result_read r = opendal_operator_blocking_read(ptr, "testpath"); + * assert(r.code == OPENDAL_OK); + * + * opendal_bytes *bytes = r.data; + * assert(bytes->len == 13); + * ``` + * + * # Safety + * + * It is **safe** under the cases below + * * The memory pointed to by `path` must contain a valid nul terminator at the end of + * the string. + * + * # Panic + * + * * If the `path` points to NULL, this function panics, i.e. exits with information */ -struct opendal_result_read opendal_operator_blocking_read(opendal_operator_ptr op_ptr, +struct opendal_result_read opendal_operator_blocking_read(struct opendal_operator_ptr ptr, const char *path); -/* - 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. - - # Safety - - It is [safe] under the cases below - * The memory pointed to by `path` must contain a valid nul terminator at the end of - the string. - - # Panic - - * If the `path` points to NULL, this function panics +/** + * \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. + * + * @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 + * @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. + * + * # 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.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); + * assert(!e.is_exist); + * ``` + * + * # Safety + * + * It is **safe** under the cases below + * * The memory pointed to by `path` must contain a valid nul terminator at the end of + * the string. + * + * # Panic + * + * * If the `path` points to NULL, this function panics, i.e. exits with information */ -struct opendal_result_is_exist opendal_operator_is_exist(opendal_operator_ptr op_ptr, +struct opendal_result_is_exist opendal_operator_is_exist(struct opendal_operator_ptr ptr, const char *path); -/* - Stat the path, return its metadata. - - If the operation succeeds, no matter the path exists or not, - the error code should be opendal_code::OPENDAL_OK. Otherwise, - the field `meata` is filled with a NULL pointer, and the error code - is set correspondingly. - - # Safety - - It is [safe] under the cases below - * The memory pointed to by `path` must contain a valid nul terminator at the end of - the string. - - # Panic - - * If the `path` points to NULL, this function panics +/** + * \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. + * + * @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. + * 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. + * + * # Example + * + * ```C + * // ... previously you wrote "Hello, World!" to path "/testpath" + * opendal_result_stat s = opendal_operator_stat(ptr, "/testpath"); + * assert(s.code == OPENDAL_OK); + * + * opendal_metadata meta = s.meta; + * + * // ... you could now use your metadata, notice that please only access metadata + * // using the APIs provided by OpenDAL + * ``` + * + * # Safety + * + * It is **safe** under the cases below + * * The memory pointed to by `path` must contain a valid nul terminator at the end of + * the string. + * + * # Panic + * + * * If the `path` points to NULL, this function panics, i.e. exits with information */ -struct opendal_result_stat opendal_operator_stat(opendal_operator_ptr op_ptr, const char *path); +struct opendal_result_stat opendal_operator_stat(struct opendal_operator_ptr ptr, const char *path); -/* - Free the allocated operator pointed by [`opendal_operator_ptr`] +/** + * \brief Free the heap-allocated operator pointed by opendal_operator_ptr. + * + * Please only use this for a pointer pointing at a valid opendal_operator_ptr. + * Calling this function on NULL does nothing, but calling this function on pointers + * of other type will lead to segfault. + * + * # Example + * + * ```C + * opendal_operator_ptr ptr = opendal_operator_new("fs", NULL); + * // ... use this ptr, maybe some reads and writes + * + * // free this operator + * opendal_operator_free(&ptr); + * ``` */ -void opendal_operator_free(const opendal_operator_ptr *self); +void opendal_operator_free(const struct opendal_operator_ptr *self); -/* - Frees the heap memory used by the [`opendal_bytes`] +/** + * \brief Frees the heap memory used by the opendal_bytes */ void opendal_bytes_free(const struct opendal_bytes *self); -/* - Free the allocated metadata +/** + * \brief Free the heap-allocated metadata used by opendal_metadata */ -void opendal_metadata_free(const opendal_metadata *self); +void opendal_metadata_free(const struct opendal_metadata *self); -/* - Return the content_length of the metadata +/** + * \brief Return the content_length of the metadata + * + * # Example + * ```C + * // ... previously you wrote "Hello, World!" to path "/testpath" + * opendal_result_stat s = opendal_operator_stat(ptr, "/testpath"); + * assert(s.code == OPENDAL_OK); + * + * opendal_metadata meta = s.meta; + * assert(opendal_metadata_content_length(&meta) == 13); + * ``` */ -uint64_t opendal_metadata_content_length(const opendal_metadata *self); +uint64_t opendal_metadata_content_length(const struct opendal_metadata *self); -/* - Return whether the path represents a file +/** + * \brief Return whether the path represents a file + * + * # Example + * ```C + * // ... previously you wrote "Hello, World!" to path "/testpath" + * opendal_result_stat s = opendal_operator_stat(ptr, "/testpath"); + * assert(s.code == OPENDAL_OK); + * + * opendal_metadata meta = s.meta; + * assert(opendal_metadata_is_file(&meta)); + * ``` */ -bool opendal_metadata_is_file(const opendal_metadata *self); +bool opendal_metadata_is_file(const struct opendal_metadata *self); -/* - Return whether the path represents a directory +/** + * \brief Return whether the path represents a directory + * + * # Example + * ```C + * // ... previously you wrote "Hello, World!" to path "/testpath" + * opendal_result_stat s = opendal_operator_stat(ptr, "/testpath"); + * assert(s.code == OPENDAL_OK); + * + * opendal_metadata meta = s.meta; + * + * // this is not a directory + * assert(!opendal_metadata_is_dir(&meta)); + * ``` + * + * \todo This is not a very clear example. A clearer example will be added + * after we support opendal_operator_mkdir() */ -bool opendal_metadata_is_dir(const opendal_metadata *self); +bool opendal_metadata_is_dir(const struct opendal_metadata *self); -/* - Construct a heap-allocated opendal_operator_options +/** + * \brief Construct a heap-allocated opendal_operator_options + * + * @return An empty opendal_operator_option, which could be set by + * opendal_operator_option_set(). + * + * @see opendal_operator_option_set */ -opendal_operator_options opendal_operator_options_new(void); - -/* - Set a Key-Value pair inside opendal_operator_options - - # Safety - - This function is unsafe because it dereferences and casts the raw pointers - Make sure the pointer of `key` and `value` point to a valid string. +struct opendal_operator_options opendal_operator_options_new(void); - # Example - - ```C - opendal_operator_options options = opendal_operator_options_new(); - opendal_operator_options_set(&options, "root", "/myroot"); - - // .. use your opendal_operator_options - - opendal_operator_options_free(options); - ``` +/** + * \brief Set a Key-Value pair inside opendal_operator_options + * + * # Safety + * + * This function is unsafe because it dereferences and casts the raw pointers + * Make sure the pointer of `key` and `value` point to a valid string. + * + * # Example + * + * ```C + * opendal_operator_options options = opendal_operator_options_new(); + * opendal_operator_options_set(&options, "root", "/myroot"); + * + * // .. use your opendal_operator_options + * + * opendal_operator_options_free(options); + * ``` */ -void opendal_operator_options_set(opendal_operator_options *self, +void opendal_operator_options_set(struct opendal_operator_options *self, const char *key, const char *value); -/* - Free the allocated memory used by [`opendal_operator_options`] +/** + * \brief Free the allocated memory used by [`opendal_operator_options`] */ -void opendal_operator_options_free(const opendal_operator_options *self); +void opendal_operator_options_free(const struct opendal_operator_options *self); #ifdef __cplusplus } // extern "C" diff --git a/bindings/c/src/error.rs b/bindings/c/src/error.rs index 61696a061728..8aa8c87ca497 100644 --- a/bindings/c/src/error.rs +++ b/bindings/c/src/error.rs @@ -19,15 +19,17 @@ use ::opendal as od; /// The wrapper type for opendal's error, wrapped because of the /// orphan rule -pub struct opendal_error(od::Error); +struct opendal_error(od::Error); -/// The error code for opendal APIs in C binding +/// \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 *` + // \todo: make details in the `opendal_error *` OPENDAL_ERROR, /// returning it back. For example, s3 returns an internal service error. OPENDAL_UNEXPECTED, diff --git a/bindings/c/src/lib.rs b/bindings/c/src/lib.rs index ad12e4d295db..4e367be729b0 100644 --- a/bindings/c/src/lib.rs +++ b/bindings/c/src/lib.rs @@ -18,7 +18,6 @@ #![allow(non_camel_case_types)] mod error; -mod macros; mod result; mod types; @@ -27,27 +26,42 @@ use std::os::raw::c_char; use std::str::FromStr; use ::opendal as od; -use error::opendal_code; -use result::opendal_result_is_exist; -use result::opendal_result_read; -use result::opendal_result_stat; -use types::opendal_metadata; -use types::opendal_operator_options; -use crate::types::opendal_bytes; -use crate::types::opendal_operator_ptr; +use crate::error::opendal_code; +use crate::result::{opendal_result_is_exist, opendal_result_read, opendal_result_stat}; +use crate::types::{ + opendal_bytes, opendal_metadata, opendal_operator_options, opendal_operator_ptr, +}; -/// Uses an array of key-value pairs to initialize the operator based on provided scheme. +/// \brief Construct an operator based on `scheme` and `options` +/// +/// Uses an array of key-value pairs to initialize the operator based on provided `scheme` +/// and `options`. For each scheme, i.e. Backend, different options could be set, you may +/// reference the [documentation](https://opendal.apache.org/docs/category/services/) for +/// each service, especially for the **Configuration Part**. +/// +/// @param scheme the service scheme you want to specify, e.g. "fs", "s3", "supabase" +/// @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. /// /// # Example /// -/// Following is a C example. -/// ```no_run +/// Following is an example. +/// ```C +/// // Allocate a new options /// opendal_operator_options options = opendal_operator_options_new(); +/// // Set the options you need /// opendal_operator_options_set(&options, "root", "/myroot"); /// +/// // Construct the operator based on the options and scheme /// opendal_operator_ptr ptr = opendal_operator_new("memory", options); -// free the options right away since the options is not used later on +/// +/// // you could free the options right away since the options is not used afterwards /// opendal_operator_options_free(&options); /// /// // ... your operations @@ -55,23 +69,16 @@ use crate::types::opendal_operator_ptr; /// /// # Safety /// -/// This function is unsafe because it deferences and casts the raw pointers. -/// -/// It is [safe] under two cases below -/// * The memory pointed to by `scheme` must contain a valid nul terminator at the end of +/// 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 -/// -/// # Returns -/// -/// Returns a result type [`opendal_result_op`], with operator_ptr. If the construction succeeds -/// the error is nullptr, otherwise it contains the error information. +/// * The `scheme` points to NULL, this function simply returns you a null opendal_operator_ptr. #[no_mangle] pub unsafe extern "C" fn opendal_operator_new( scheme: *const c_char, - options: opendal_operator_options, + options: *const opendal_operator_options, ) -> opendal_operator_ptr { - if scheme.is_null() || options.is_null() { + if scheme.is_null() { return opendal_operator_ptr::null(); } @@ -84,17 +91,18 @@ pub unsafe extern "C" fn opendal_operator_new( }; let mut map = HashMap::default(); - for (k, v) in options.as_ref() { - map.insert(k.to_string(), v.to_string()); + if !options.is_null() { + for (k, v) in (*options).as_ref() { + map.insert(k.to_string(), v.to_string()); + } } - let op = match scheme { - od::Scheme::Memory => generate_operator!(od::services::Memory, map), - _ => { + let op = match od::Operator::via_map(scheme, map) { + Ok(o) => o.blocking(), + Err(_) => { return opendal_operator_ptr::null(); } - } - .blocking(); + }; // this prevents the operator memory from being dropped by the Box let op = Box::leak(Box::new(op)); @@ -102,21 +110,50 @@ pub unsafe extern "C" fn opendal_operator_new( opendal_operator_ptr::from(op) } -/// Write the data into the path blockingly by operator, returns the error code OPENDAL_OK +/// \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 /// +/// @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 +/// +/// # Example +/// +/// Following is an example +/// ```C +/// //...prepare your opendal_operator_ptr, named ptr for example +/// +/// // prepare your data +/// char* data = "Hello, World!"; +/// 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); +/// +/// // Assert that this succeeds +/// assert(code == OPENDAL_OK) +/// ``` +/// /// # Safety /// -/// It is [safe] under the cases below +/// It is **safe** under the cases below /// * The memory pointed to by `path` must contain a valid nul terminator at the end of /// the string. +/// * The `bytes` provided has valid byte in the `data` field and the `len` field is set +/// correctly. /// /// # Panic /// -/// * If the `path` points to NULL, this function panics +/// * If the `path` points to NULL, this function panics, i.e. exits with information #[no_mangle] pub unsafe extern "C" fn opendal_operator_blocking_write( - op_ptr: opendal_operator_ptr, + ptr: opendal_operator_ptr, path: *const c_char, bytes: opendal_bytes, ) -> opendal_code { @@ -124,7 +161,7 @@ pub unsafe extern "C" fn opendal_operator_blocking_write( panic!("The path given is pointing at NULL"); } - let op = op_ptr.get_ref(); + 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, @@ -132,29 +169,55 @@ pub unsafe extern "C" fn opendal_operator_blocking_write( } } -/// Read the data out from path into a [`Bytes`] blockingly by operator, returns -/// a result with error code. If the error code is not OPENDAL_OK, the `data` field -/// of the result points to NULL. +/// \brief Blockingly read the data from `path`. +/// +/// Read the data out from `path` blockingly by operator, returns +/// an opendal_result_read with error code. +/// +/// @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 +/// @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. +/// +/// \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. +/// +/// # Example +/// +/// Following is an example +/// ```C +/// // ... you have write "Hello, World!" to path "/testpath" +/// +/// opendal_result_read r = opendal_operator_blocking_read(ptr, "testpath"); +/// assert(r.code == OPENDAL_OK); +/// +/// opendal_bytes *bytes = r.data; +/// assert(bytes->len == 13); +/// ``` /// /// # Safety /// -/// It is [safe] under the cases below +/// It is **safe** under the cases below /// * The memory pointed to by `path` must contain a valid nul terminator at the end of /// the string. /// /// # Panic /// -/// * If the `path` points to NULL, this function panics +/// * If the `path` points to NULL, this function panics, i.e. exits with information #[no_mangle] pub unsafe extern "C" fn opendal_operator_blocking_read( - op_ptr: opendal_operator_ptr, + ptr: opendal_operator_ptr, path: *const c_char, ) -> opendal_result_read { if path.is_null() { panic!("The path given is pointing at NULL"); } - let op = op_ptr.get_ref(); + let op = ptr.as_ref(); let path = unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() }; let data = op.read(path); match data { @@ -172,32 +235,55 @@ pub unsafe extern "C" fn opendal_operator_blocking_read( } } -/// Check whether the path exists. +/// \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. /// +/// @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 +/// @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. +/// +/// # 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.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); +/// assert(!e.is_exist); +/// ``` +/// /// # Safety /// -/// It is [safe] under the cases below +/// It is **safe** under the cases below /// * The memory pointed to by `path` must contain a valid nul terminator at the end of /// the string. /// /// # Panic /// -/// * If the `path` points to NULL, this function panics +/// * If the `path` points to NULL, this function panics, i.e. exits with information #[no_mangle] pub unsafe extern "C" fn opendal_operator_is_exist( - op_ptr: opendal_operator_ptr, + ptr: opendal_operator_ptr, path: *const c_char, ) -> opendal_result_is_exist { if path.is_null() { panic!("The path given is pointing at NULL"); } - let op = op_ptr.get_ref(); + let op = ptr.as_ref(); let path = unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() }; match op.is_exist(path) { Ok(e) => opendal_result_is_exist { @@ -211,32 +297,54 @@ pub unsafe extern "C" fn opendal_operator_is_exist( } } -/// Stat the path, return its metadata. +/// \brief Stat the path, return its metadata. /// -/// If the operation succeeds, no matter the path exists or not, -/// the error code should be opendal_code::OPENDAL_OK. Otherwise, -/// the field `meata` is filled with a NULL pointer, and the error code -/// is set correspondingly. +/// 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. +/// +/// @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. +/// 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. +/// +/// # Example +/// +/// ```C +/// // ... previously you wrote "Hello, World!" to path "/testpath" +/// opendal_result_stat s = opendal_operator_stat(ptr, "/testpath"); +/// assert(s.code == OPENDAL_OK); +/// +/// opendal_metadata meta = s.meta; +/// +/// // ... you could now use your metadata, notice that please only access metadata +/// // using the APIs provided by OpenDAL +/// ``` /// /// # Safety /// -/// It is [safe] under the cases below +/// It is **safe** under the cases below /// * The memory pointed to by `path` must contain a valid nul terminator at the end of /// the string. /// /// # Panic /// -/// * If the `path` points to NULL, this function panics +/// * If the `path` points to NULL, this function panics, i.e. exits with information #[no_mangle] pub unsafe extern "C" fn opendal_operator_stat( - op_ptr: opendal_operator_ptr, + ptr: opendal_operator_ptr, path: *const c_char, ) -> opendal_result_stat { if path.is_null() { panic!("The path given is pointing at NULL"); } - let op = op_ptr.get_ref(); + let op = ptr.as_ref(); let path = unsafe { std::ffi::CStr::from_ptr(path).to_str().unwrap() }; match op.stat(path) { Ok(m) => opendal_result_stat { diff --git a/bindings/c/src/macros.rs b/bindings/c/src/macros.rs deleted file mode 100644 index f77811d1abe1..000000000000 --- a/bindings/c/src/macros.rs +++ /dev/null @@ -1,31 +0,0 @@ -// 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. - -/// Macro used to generate operator upon construction and return C-compatible -/// error if failed -#[macro_export] -macro_rules! generate_operator { - ($type:ty, $map:expr) => {{ - let b = od::Operator::from_map::<$type>($map); - match b { - Ok(b) => b.finish(), - Err(_) => { - return opendal_operator_ptr::null(); - } - } - }}; -} diff --git a/bindings/c/src/result.rs b/bindings/c/src/result.rs index 74f0b9a58128..345d3d3539f6 100644 --- a/bindings/c/src/result.rs +++ b/bindings/c/src/result.rs @@ -24,30 +24,44 @@ use crate::error::opendal_code; use crate::types::opendal_bytes; use crate::types::opendal_metadata; -/// The Rust-like Result type of opendal C binding, it contains -/// the data that the read operation returns and a error code +/// \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. /// If the read operation failed, the `data` fields should be a nullptr -/// and the error code is NOT OPENDAL_OK. +/// and the error code is **NOT** OPENDAL_OK. #[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 result type for [`opendal_operator_is_exist()`], the field `is_exist` +/// \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. +/// +/// \note If the opendal_operator_is_exist() fails, the `is_exist` field +/// will be set to false. #[repr(C)] 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 result type for [`opendal_operator_stat()`], the meta contains the metadata -/// of the path, the code represents whether the stat operation is successful. Note -/// that the operation could be successful even if the path does not exist. +/// \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. #[repr(C)] pub struct opendal_result_stat { + /// The metadata output of the stat pub meta: opendal_metadata, + /// The error code, should be OPENDAL_OK if succeeds pub code: opendal_code, } diff --git a/bindings/c/src/types.rs b/bindings/c/src/types.rs index 8c47bcbf54af..a0346a7a905b 100644 --- a/bindings/c/src/types.rs +++ b/bindings/c/src/types.rs @@ -20,21 +20,40 @@ use std::os::raw::c_char; use ::opendal as od; -/// The [`opendal_operator_ptr`] owns a pointer to a [`od::BlockingOperator`]. -/// It is also the key struct that OpenDAL's APIs access the real -/// operator's memory. The use of OperatorPtr is zero cost, it -/// only returns a reference of the underlying Operator. +/// \brief Used to access almost all OpenDAL APIs. It represents a +/// operator that provides the unified interfaces provided by OpenDAL. /// -/// The [`opendal_operator_ptr`] also has a transparent layout, allowing you -/// to check its validity by native boolean operator. -/// e.g. you could check by (!ptr) on a [`opendal_operator_ptr`] -#[repr(transparent)] +/// @see opendal_operator_new This function construct the operator +/// @see opendal_operator_free This function frees the heap memory of the operator +/// +/// \note The opendal_operator_ptr actually owns a pointer to +/// a opendal::BlockingOperator, which is inside the Rust core code. +/// +/// \remark You may use the field `ptr` to check whether this is a NULL +/// operator. +#[repr(C)] pub struct opendal_operator_ptr { + /// The pointer to the opendal::BlockingOperator in the Rust code. + /// Only touch this on judging whether it is NULL. ptr: *const od::BlockingOperator, } impl opendal_operator_ptr { - /// Free the allocated operator pointed by [`opendal_operator_ptr`] + /// \brief Free the heap-allocated operator pointed by opendal_operator_ptr. + /// + /// Please only use this for a pointer pointing at a valid opendal_operator_ptr. + /// Calling this function on NULL does nothing, but calling this function on pointers + /// of other type will lead to segfault. + /// + /// # Example + /// + /// ```C + /// opendal_operator_ptr ptr = opendal_operator_new("fs", NULL); + /// // ... use this ptr, maybe some reads and writes + /// + /// // free this operator + /// opendal_operator_free(&ptr); + /// ``` #[no_mangle] pub extern "C" fn opendal_operator_free(&self) { if self.is_null() { @@ -46,9 +65,7 @@ impl opendal_operator_ptr { impl opendal_operator_ptr { /// Creates an OperatorPtr will nullptr, indicating this [`opendal_operator_ptr`] - /// is invalid. The `transparent` layout also guarantees that if the - /// underlying field `ptr` is a nullptr, the [`opendal_operator_ptr`] has the - /// same layout as the nullptr. + /// is invalid. pub(crate) fn null() -> Self { Self { ptr: std::ptr::null(), @@ -61,7 +78,7 @@ impl opendal_operator_ptr { } /// Returns a reference to the underlying [`od::BlockingOperator`] - pub(crate) fn get_ref(&self) -> &od::BlockingOperator { + pub(crate) fn as_ref(&self) -> &od::BlockingOperator { unsafe { &*(self.ptr) } } } @@ -80,26 +97,23 @@ impl From<&mut od::BlockingOperator> for opendal_operator_ptr { } } -/// The [`opendal_bytes`] type is a C-compatible substitute for [`Vec`] -/// in Rust, it will not be deallocated automatically like what -/// has been done in Rust. Instead, you have to call [`opendal_free_bytes`] +/// \brief opendal_bytes carries raw-bytes with its length +/// +/// The opendal_bytes type is a C-compatible substitute for Vec type +/// in Rust, it has to be manually freed. You have to call opendal_bytes_free() /// to free the heap memory to avoid memory leak. +/// +/// @see opendal_bytes_free #[repr(C)] pub struct opendal_bytes { + /// Pointing to the byte array on heap pub data: *const u8, + /// The length of the byte array pub len: usize, } impl opendal_bytes { - /// Construct a [`opendal_bytes`] from the Rust [`Vec`] of bytes - pub(crate) fn from_vec(vec: Vec) -> Self { - let data = vec.as_ptr() as *const u8; - let len = vec.len(); - std::mem::forget(vec); // To avoid deallocation of the vec. - Self { data, len } - } - - /// Frees the heap memory used by the [`opendal_bytes`] + /// \brief Frees the heap memory used by the opendal_bytes #[no_mangle] pub extern "C" fn opendal_bytes_free(&self) { unsafe { @@ -110,6 +124,16 @@ impl opendal_bytes { } } +impl opendal_bytes { + /// Construct a [`opendal_bytes`] from the Rust [`Vec`] of bytes + pub(crate) fn from_vec(vec: Vec) -> Self { + let data = vec.as_ptr() as *const u8; + let len = vec.len(); + std::mem::forget(vec); // To avoid deallocation of the vec. + Self { data, len } + } +} + #[allow(clippy::from_over_into)] impl Into for opendal_bytes { fn into(self) -> bytes::Bytes { @@ -118,20 +142,24 @@ impl Into for opendal_bytes { } } -/// Metadata carries all metadata associated with an path. +/// \brief Carries all metadata associated with a path. +/// +/// The metadata of the "thing" under a path. Please **only** use the opendal_metadata +/// with our provided API, e.g. opendal_metadata_content_length(). /// -/// # Notes +/// \note The metadata is also heap-allocated, please call opendal_metadata_free() on this +/// to free the heap memory. /// -/// mode and content_length are required metadata that all services -/// should provide during `stat` operation. But in `list` operation, -/// a.k.a., `Entry`'s content length could be NULL. -#[repr(transparent)] +/// @see opendal_metadata_free +#[repr(C)] pub struct opendal_metadata { + /// The pointer to the opendal::Metadata in the Rust code. + /// Only touch this on judging whether it is NULL. pub inner: *const od::Metadata, } impl opendal_metadata { - /// Free the allocated metadata + /// \brief Free the heap-allocated metadata used by opendal_metadata #[no_mangle] pub extern "C" fn opendal_metadata_free(&self) { if self.inner.is_null() { @@ -140,7 +168,17 @@ impl opendal_metadata { let _ = unsafe { Box::from_raw(self.inner as *mut od::Metadata) }; } - /// Return the content_length of the metadata + /// \brief Return the content_length of the metadata + /// + /// # Example + /// ```C + /// // ... previously you wrote "Hello, World!" to path "/testpath" + /// opendal_result_stat s = opendal_operator_stat(ptr, "/testpath"); + /// assert(s.code == OPENDAL_OK); + /// + /// opendal_metadata meta = s.meta; + /// assert(opendal_metadata_content_length(&meta) == 13); + /// ``` #[no_mangle] pub extern "C" fn opendal_metadata_content_length(&self) -> u64 { // Safety: the inner should never be null once constructed @@ -148,7 +186,17 @@ impl opendal_metadata { unsafe { (*self.inner).content_length() } } - /// Return whether the path represents a file + /// \brief Return whether the path represents a file + /// + /// # Example + /// ```C + /// // ... previously you wrote "Hello, World!" to path "/testpath" + /// opendal_result_stat s = opendal_operator_stat(ptr, "/testpath"); + /// assert(s.code == OPENDAL_OK); + /// + /// opendal_metadata meta = s.meta; + /// assert(opendal_metadata_is_file(&meta)); + /// ``` #[no_mangle] pub extern "C" fn opendal_metadata_is_file(&self) -> bool { // Safety: the inner should never be null once constructed @@ -156,7 +204,22 @@ impl opendal_metadata { unsafe { (*self.inner).is_file() } } - /// Return whether the path represents a directory + /// \brief Return whether the path represents a directory + /// + /// # Example + /// ```C + /// // ... previously you wrote "Hello, World!" to path "/testpath" + /// opendal_result_stat s = opendal_operator_stat(ptr, "/testpath"); + /// assert(s.code == OPENDAL_OK); + /// + /// opendal_metadata meta = s.meta; + /// + /// // this is not a directory + /// assert(!opendal_metadata_is_dir(&meta)); + /// ``` + /// + /// \todo This is not a very clear example. A clearer example will be added + /// after we support opendal_operator_mkdir() #[no_mangle] pub extern "C" fn opendal_metadata_is_dir(&self) -> bool { // Safety: the inner should never be null once constructed @@ -182,14 +245,28 @@ impl opendal_metadata { } } -/// [`opendal_operator_options`] represents a series of string type key-value pairs, it may be used for initialization -#[repr(transparent)] +/// \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 +#[repr(C)] pub struct opendal_operator_options { + /// The pointer to the Rust HashMap + /// Only touch this on judging whether it is NULL. inner: *mut HashMap, } impl opendal_operator_options { - /// Construct a heap-allocated opendal_operator_options + /// \brief Construct a heap-allocated opendal_operator_options + /// + /// @return An empty opendal_operator_option, which could be set by + /// opendal_operator_option_set(). + /// + /// @see opendal_operator_option_set #[no_mangle] pub extern "C" fn opendal_operator_options_new() -> Self { let map: HashMap = HashMap::default(); @@ -198,7 +275,7 @@ impl opendal_operator_options { } } - /// Set a Key-Value pair inside opendal_operator_options + /// \brief Set a Key-Value pair inside opendal_operator_options /// /// # Safety /// @@ -237,12 +314,7 @@ impl opendal_operator_options { unsafe { &*(self.inner) } } - /// Returns whether the underlying HashMap points to NULL - pub(crate) fn is_null(&self) -> bool { - self.inner.is_null() - } - - /// Free the allocated memory used by [`opendal_operator_options`] + /// \brief Free the allocated memory used by [`opendal_operator_options`] #[no_mangle] pub extern "C" fn opendal_operator_options_free(&self) { if self.inner.is_null() { diff --git a/bindings/c/tests/bdd.cpp b/bindings/c/tests/bdd.cpp index 3b3dd497cea7..e28798725573 100644 --- a/bindings/c/tests/bdd.cpp +++ b/bindings/c/tests/bdd.cpp @@ -40,8 +40,8 @@ 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); + this->p = opendal_operator_new(scheme.c_str(), &options); + EXPECT_TRUE(this->p.ptr); opendal_operator_options_free(&options); } diff --git a/licenserc.toml b/licenserc.toml index bbd10e576b9e..92975b72bebb 100644 --- a/licenserc.toml +++ b/licenserc.toml @@ -36,3 +36,8 @@ excludes = [ [properties] inceptionYear = 2023 + +# allow SLASH_STAR_STYLE licence header for c code and headers +[mapping.SLASHSTAR_STYLE] +extensions = ["c", "h"] +