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

paleolimbot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-nanoarrow.git


The following commit(s) were added to refs/heads/main by this push:
     new 15019d6  Add more C++ and error handling helpers (#189)
15019d6 is described below

commit 15019d6680367835a8082e847468ed0f9d3b7530
Author: Dewey Dunnington <[email protected]>
AuthorDate: Tue May 9 09:46:19 2023 -0400

    Add more C++ and error handling helpers (#189)
    
    This PR is motivated by reading some early usage of nanoarrow (including
    some of my own!). Basically, I just want to make it really easy to
    communicate failed calls to the nanoarrow API, particularly for those
    not used to C-style return code error handling. This PR:
    
    - Adds `NANOARROW_THROW_NOT_OK()` in the C++ helper
    - Adds `NANOARROW_ASSERT_OK()` in the C header
    - Adds `NANOARROW_DEBUG` mode and inserts some more information when
    compiled with this define
    - Adds C++ helpers for the IPC extension to mirror the C API helpers
    
    In writing and documenting the helpers, I solidifed some
    guarantees...notably, that `ArrowError` has a nul-terminated error
    message whenever an error code is returned.
---
 .github/workflows/build-and-test.yaml              |  5 +-
 .github/workflows/packaging.yaml                   |  5 --
 CMakeLists.txt                                     |  3 +
 docs/source/cpp.rst                                |  6 ++
 docs/source/ipc.rst                                | 10 +++
 extensions/nanoarrow_ipc/CMakeLists.txt            |  5 ++
 .../nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc.hpp  | 85 ++++++++++++++++++++++
 .../src/nanoarrow/nanoarrow_ipc_decoder.c          | 18 +++--
 .../src/nanoarrow/nanoarrow_ipc_hpp_test.cc        | 46 ++++++++++++
 .../src/nanoarrow/nanoarrow_ipc_reader.c           | 20 +++--
 src/nanoarrow/array.c                              |  6 +-
 src/nanoarrow/nanoarrow.h                          | 27 ++++++-
 src/nanoarrow/nanoarrow.hpp                        | 50 +++++++++++++
 src/nanoarrow/nanoarrow_hpp_test.cc                | 10 +++
 src/nanoarrow/nanoarrow_types.h                    | 67 +++++++++++++++++
 src/nanoarrow/utils.c                              |  8 +-
 src/nanoarrow/utils_test.cc                        | 29 +++++++-
 17 files changed, 371 insertions(+), 29 deletions(-)

diff --git a/.github/workflows/build-and-test.yaml 
b/.github/workflows/build-and-test.yaml
index b3857fc..abe54e4 100644
--- a/.github/workflows/build-and-test.yaml
+++ b/.github/workflows/build-and-test.yaml
@@ -40,7 +40,8 @@ jobs:
       fail-fast: false
       matrix:
         config:
-          - {label: default-build}
+          - {label: default-build, cmake_args: "-DCMAKE_BUILD_TYPE=Debug"}
+          - {label: release-build}
           - {label: namespaced-build, cmake_args: 
"-DNANOARROW_NAMESPACE=SomeUserNamespace"}
           - {label: bundled-build, cmake_args: "-DNANOARROW_BUNDLE=ON"}
           - {label: bundled-cpp-build, cmake_args: "-DNANOARROW_BUNDLE=ON 
-DNANOARROW_BUNDLE_AS_CPP=ON"}
@@ -67,7 +68,7 @@ jobs:
           sudo ldconfig
           mkdir build
           cd build
-          cmake .. -DCMAKE_BUILD_TYPE=Debug -DNANOARROW_BUILD_TESTS=ON ${{ 
matrix.config.cmake_args }}
+          cmake .. -DNANOARROW_BUILD_TESTS=ON ${{ matrix.config.cmake_args }}
           cmake --build .
 
       - name: Check for non-namespaced symbols in namespaced build
diff --git a/.github/workflows/packaging.yaml b/.github/workflows/packaging.yaml
index 85a30ed..a778c96 100644
--- a/.github/workflows/packaging.yaml
+++ b/.github/workflows/packaging.yaml
@@ -127,11 +127,6 @@ jobs:
           docker compose run --rm docs
           popd
 
-      - name: Documentation build logs
-        if: always()
-        run: |
-          cat nanoarrow/docs/_build/build-docs.log
-
       - name: Compress docs
         shell: bash
         run: |
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 01b96dc..4a72f2c 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -91,6 +91,7 @@ if(NANOARROW_BUNDLE)
     if(NANOARROW_BUILD_TESTS)
         include_directories(${CMAKE_BINARY_DIR}/amalgamation)
         add_library(nanoarrow ${NANOARROW_C_TEMP})
+        target_compile_definitions(nanoarrow PUBLIC 
"$<$<CONFIG:Debug>:NANOARROW_DEBUG>")
     endif()
 
     # Install the amalgamated header and source
@@ -107,6 +108,8 @@ else()
         $<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/src>
         $<INSTALL_INTERFACE:include>)
     target_include_directories(nanoarrow PUBLIC 
$<BUILD_INTERFACE:${CMAKE_CURRENT_BINARY_DIR}/generated>)
+    target_compile_definitions(nanoarrow PUBLIC 
"$<$<CONFIG:Debug>:NANOARROW_DEBUG>")
+
     if(CMAKE_BUILD_TYPE STREQUAL "Debug")
         if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
             target_compile_options(
diff --git a/docs/source/cpp.rst b/docs/source/cpp.rst
index 44b7b4e..615262f 100644
--- a/docs/source/cpp.rst
+++ b/docs/source/cpp.rst
@@ -20,6 +20,12 @@ C++ API Reference
 
 .. doxygengroup:: nanoarrow_hpp
 
+Error handling
+--------------
+
+.. doxygengroup:: nanoarrow_hpp-errors
+   :members:
+
 Owning object wrappers
 ----------------------
 .. doxygengroup:: nanoarrow_hpp-unique
diff --git a/docs/source/ipc.rst b/docs/source/ipc.rst
index 8bc7214..fb009c1 100644
--- a/docs/source/ipc.rst
+++ b/docs/source/ipc.rst
@@ -18,6 +18,16 @@
 IPC Extension Reference
 =======================
 
+C API
+------------------------
+
 .. doxygengroup:: nanoarrow_ipc
    :project: nanoarrow_ipc
    :members:
+
+C++ Helpers
+------------------------
+
+.. doxygengroup:: nanoarrow_ipc_hpp-unique
+   :project: nanoarrow_ipc
+   :members:
diff --git a/extensions/nanoarrow_ipc/CMakeLists.txt 
b/extensions/nanoarrow_ipc/CMakeLists.txt
index c0b335c..92a85fa 100644
--- a/extensions/nanoarrow_ipc/CMakeLists.txt
+++ b/extensions/nanoarrow_ipc/CMakeLists.txt
@@ -212,6 +212,7 @@ if (NANOARROW_IPC_BUILD_TESTS)
   add_executable(nanoarrow_ipc_decoder_test 
src/nanoarrow/nanoarrow_ipc_decoder_test.cc)
   add_executable(nanoarrow_ipc_reader_test 
src/nanoarrow/nanoarrow_ipc_reader_test.cc)
   add_executable(nanoarrow_ipc_files_test 
src/nanoarrow/nanoarrow_ipc_files_test.cc)
+  add_executable(nanoarrow_ipc_hpp_test 
src/nanoarrow/nanoarrow_ipc_hpp_test.cc)
 
   if(NANOARROW_IPC_CODE_COVERAGE)
     target_compile_options(ipc_coverage_config INTERFACE -O0 -g --coverage)
@@ -236,11 +237,15 @@ if (NANOARROW_IPC_BUILD_TESTS)
   target_link_libraries(
     nanoarrow_ipc_files_test
     nanoarrow_ipc nanoarrow ${NANOARROW_IPC_ARROW_TARGET} gtest_main 
ipc_coverage_config)
+  target_link_libraries(
+      nanoarrow_ipc_hpp_test
+      nanoarrow_ipc nanoarrow ${NANOARROW_IPC_ARROW_TARGET} gtest_main 
ipc_coverage_config)
 
   include(GoogleTest)
   gtest_discover_tests(nanoarrow_ipc_decoder_test)
   gtest_discover_tests(nanoarrow_ipc_reader_test)
   gtest_discover_tests(nanoarrow_ipc_files_test)
+  gtest_discover_tests(nanoarrow_ipc_hpp_test)
 endif()
 
 if (NANOARROW_IPC_BUILD_APPS)
diff --git a/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc.hpp 
b/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc.hpp
new file mode 100644
index 0000000..505f827
--- /dev/null
+++ b/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc.hpp
@@ -0,0 +1,85 @@
+// 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 "nanoarrow_ipc.h"
+
+#ifndef NANOARROW_IPC_HPP_INCLUDED
+#define NANOARROW_IPC_HPP_INCLUDED
+
+namespace nanoarrow {
+
+namespace internal {
+
+static inline void init_pointer(struct ArrowIpcDecoder* data) {
+  data->private_data = nullptr;
+}
+
+static inline void move_pointer(struct ArrowIpcDecoder* src,
+                                struct ArrowIpcDecoder* dst) {
+  memcpy(dst, src, sizeof(struct ArrowIpcDecoder));
+  src->private_data = nullptr;
+}
+
+static inline void release_pointer(struct ArrowIpcDecoder* data) {
+  ArrowIpcDecoderReset(data);
+}
+
+static inline void init_pointer(struct ArrowIpcInputStream* data) {
+  data->release = nullptr;
+}
+
+static inline void move_pointer(struct ArrowIpcInputStream* src,
+                                struct ArrowIpcInputStream* dst) {
+  memcpy(dst, src, sizeof(struct ArrowIpcInputStream));
+  src->release = nullptr;
+}
+
+static inline void release_pointer(struct ArrowIpcInputStream* data) {
+  if (data->release != nullptr) {
+    data->release(data);
+  }
+}
+
+}  // namespace internal
+}  // namespace nanoarrow
+
+#include "nanoarrow.hpp"
+
+namespace nanoarrow {
+
+namespace ipc {
+
+/// \defgroup nanoarrow_ipc_hpp-unique Unique object wrappers
+///
+/// Extends the unique object wrappers in nanoarrow.hpp to include C structs
+/// defined in the nanoarrow_ipc.h header.
+///
+/// @{
+
+/// \brief Class wrapping a unique struct ArrowIpcDecoder
+using UniqueDecoder = internal::Unique<struct ArrowIpcDecoder>;
+
+/// \brief Class wrapping a unique struct ArrowIpcInputStream
+using UniqueInputStream = internal::Unique<struct ArrowIpcInputStream>;
+
+/// @}
+
+}  // namespace ipc
+
+}  // namespace nanoarrow
+
+#endif
diff --git a/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_decoder.c 
b/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_decoder.c
index 1d78e96..e401cac 100644
--- a/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_decoder.c
+++ b/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_decoder.c
@@ -212,15 +212,17 @@ void ArrowIpcDecoderReset(struct ArrowIpcDecoder* 
decoder) {
   struct ArrowIpcDecoderPrivate* private_data =
       (struct ArrowIpcDecoderPrivate*)decoder->private_data;
 
-  ArrowArrayViewReset(&private_data->array_view);
+  if (private_data != NULL) {
+    ArrowArrayViewReset(&private_data->array_view);
 
-  if (private_data->fields != NULL) {
-    ArrowFree(private_data->fields);
-    private_data->n_fields = 0;
-  }
+    if (private_data->fields != NULL) {
+      ArrowFree(private_data->fields);
+      private_data->n_fields = 0;
+    }
 
-  ArrowFree(private_data);
-  memset(decoder, 0, sizeof(struct ArrowIpcDecoder));
+    ArrowFree(private_data);
+    memset(decoder, 0, sizeof(struct ArrowIpcDecoder));
+  }
 }
 
 static inline uint32_t ArrowIpcReadContinuationBytes(struct ArrowBufferView* 
data) {
@@ -1222,7 +1224,7 @@ static ArrowErrorCode ArrowIpcMakeBufferFromView(struct 
ArrowIpcBufferFactory* f
   view.size_bytes = src->buffer_length_bytes;
 
   ArrowBufferInit(dst);
-  NANOARROW_RETURN_NOT_OK(ArrowBufferAppendBufferView(dst, view));
+  NANOARROW_RETURN_NOT_OK_WITH_ERROR(ArrowBufferAppendBufferView(dst, view), 
error);
   return NANOARROW_OK;
 }
 
diff --git a/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_hpp_test.cc 
b/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_hpp_test.cc
new file mode 100644
index 0000000..38edb8c
--- /dev/null
+++ b/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_hpp_test.cc
@@ -0,0 +1,46 @@
+// 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>
+
+#include "nanoarrow_ipc.hpp"
+
+TEST(NanoarrowIpcHppTest, NanoarrowIpcHppTestUniqueDecoder) {
+  nanoarrow::ipc::UniqueDecoder decoder;
+
+  EXPECT_EQ(decoder->private_data, nullptr);
+  ASSERT_EQ(ArrowIpcDecoderInit(decoder.get()), NANOARROW_OK);
+  EXPECT_NE(decoder->private_data, nullptr);
+
+  nanoarrow::ipc::UniqueDecoder decoder2 = std::move(decoder);
+  EXPECT_NE(decoder2->private_data, nullptr);
+  EXPECT_EQ(decoder->private_data, nullptr);
+}
+
+TEST(NanoarrowIpcHppTest, NanoarrowIpcHppTestUniqueInputStream) {
+  nanoarrow::ipc::UniqueInputStream input;
+  nanoarrow::UniqueBuffer buf;
+  ASSERT_EQ(ArrowBufferAppend(buf.get(), "abcdefg", 7), NANOARROW_OK);
+
+  EXPECT_EQ(input->release, nullptr);
+  ASSERT_EQ(ArrowIpcInputStreamInitBuffer(input.get(), buf.get()), 
NANOARROW_OK);
+  EXPECT_NE(input->release, nullptr);
+
+  nanoarrow::ipc::UniqueInputStream input2 = std::move(input);
+  EXPECT_NE(input2->release, nullptr);
+  EXPECT_EQ(input->release, nullptr);
+}
diff --git a/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_reader.c 
b/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_reader.c
index 0e457c7..0e72b3d 100644
--- a/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_reader.c
+++ b/extensions/nanoarrow_ipc/src/nanoarrow/nanoarrow_ipc_reader.c
@@ -202,7 +202,8 @@ static int ArrowIpcArrayStreamReaderNextHeader(
   int64_t bytes_read = 0;
 
   // Read 8 bytes (continuation + header size in bytes)
-  NANOARROW_RETURN_NOT_OK(ArrowBufferReserve(&private_data->header, 8));
+  NANOARROW_RETURN_NOT_OK_WITH_ERROR(ArrowBufferReserve(&private_data->header, 
8),
+                                     &private_data->error);
   NANOARROW_RETURN_NOT_OK(private_data->input.read(&private_data->input,
                                                    private_data->header.data, 
8,
                                                    &bytes_read, 
&private_data->error));
@@ -233,8 +234,9 @@ static int ArrowIpcArrayStreamReaderNextHeader(
 
   // Read the header bytes
   int64_t expected_header_bytes = private_data->decoder.header_size_bytes - 8;
-  NANOARROW_RETURN_NOT_OK(
-      ArrowBufferReserve(&private_data->header, expected_header_bytes));
+  NANOARROW_RETURN_NOT_OK_WITH_ERROR(
+      ArrowBufferReserve(&private_data->header, expected_header_bytes),
+      &private_data->error);
   NANOARROW_RETURN_NOT_OK(
       private_data->input.read(&private_data->input, private_data->header.data 
+ 8,
                                expected_header_bytes, &bytes_read, 
&private_data->error));
@@ -264,7 +266,8 @@ static int ArrowIpcArrayStreamReaderNextBody(
 
   // Read the body bytes
   private_data->body.size_bytes = 0;
-  NANOARROW_RETURN_NOT_OK(ArrowBufferReserve(&private_data->body, 
bytes_to_read));
+  NANOARROW_RETURN_NOT_OK_WITH_ERROR(
+      ArrowBufferReserve(&private_data->body, bytes_to_read), 
&private_data->error);
   NANOARROW_RETURN_NOT_OK(private_data->input.read(&private_data->input,
                                                    private_data->body.data, 
bytes_to_read,
                                                    &bytes_read, 
&private_data->error));
@@ -304,8 +307,10 @@ static int ArrowIpcArrayStreamReaderReadSchemaIfNeeded(
   }
 
   // Notify the decoder of buffer endianness
-  NANOARROW_RETURN_NOT_OK(ArrowIpcDecoderSetEndianness(&private_data->decoder,
-                                                       
private_data->decoder.endianness));
+  NANOARROW_RETURN_NOT_OK_WITH_ERROR(
+      ArrowIpcDecoderSetEndianness(&private_data->decoder,
+                                   private_data->decoder.endianness),
+      &private_data->error);
 
   struct ArrowSchema tmp;
   NANOARROW_RETURN_NOT_OK(
@@ -369,7 +374,8 @@ static int ArrowIpcArrayStreamReaderGetNext(struct 
ArrowArrayStream* stream,
 
   if (private_data->use_shared_buffers) {
     struct ArrowIpcSharedBuffer shared;
-    NANOARROW_RETURN_NOT_OK(ArrowIpcSharedBufferInit(&shared, 
&private_data->body));
+    NANOARROW_RETURN_NOT_OK_WITH_ERROR(
+        ArrowIpcSharedBufferInit(&shared, &private_data->body), 
&private_data->error);
     NANOARROW_RETURN_NOT_OK(ArrowIpcDecoderDecodeArrayFromShared(
         &private_data->decoder, &shared, private_data->field_index, &tmp,
         &private_data->error));
diff --git a/src/nanoarrow/array.c b/src/nanoarrow/array.c
index ae1db4d..403b4b0 100644
--- a/src/nanoarrow/array.c
+++ b/src/nanoarrow/array.c
@@ -441,7 +441,7 @@ ArrowErrorCode ArrowArrayFinishBuilding(struct ArrowArray* 
array,
   // in some implementations (at least one version of Arrow C++ at the time 
this
   // was added). Only do this fix if we can assume CPU data access.
   if (validation_level >= NANOARROW_VALIDATION_LEVEL_DEFAULT) {
-    NANOARROW_RETURN_NOT_OK(ArrowArrayFinalizeBuffers(array));
+    NANOARROW_RETURN_NOT_OK_WITH_ERROR(ArrowArrayFinalizeBuffers(array), 
error);
   }
 
   // Make sure the value we get with array->buffers[i] is set to the actual
@@ -456,7 +456,8 @@ ArrowErrorCode ArrowArrayFinishBuilding(struct ArrowArray* 
array,
   // into the wild that is going to segfault
   struct ArrowArrayView array_view;
 
-  NANOARROW_RETURN_NOT_OK(ArrowArrayViewInitFromArray(&array_view, array));
+  NANOARROW_RETURN_NOT_OK_WITH_ERROR(ArrowArrayViewInitFromArray(&array_view, 
array),
+                                     error);
 
   // Check buffer sizes once without using internal buffer data since
   // ArrowArrayViewSetArray() assumes that all the buffers are long enough
@@ -550,6 +551,7 @@ ArrowErrorCode ArrowArrayViewInitFromSchema(struct 
ArrowArrayView* array_view,
 
   result = ArrowArrayViewAllocateChildren(array_view, schema->n_children);
   if (result != NANOARROW_OK) {
+    ArrowErrorSet(error, "ArrowArrayViewAllocateChildren() failed");
     ArrowArrayViewReset(array_view);
     return result;
   }
diff --git a/src/nanoarrow/nanoarrow.h b/src/nanoarrow/nanoarrow.h
index 09ee85d..7b11825 100644
--- a/src/nanoarrow/nanoarrow.h
+++ b/src/nanoarrow/nanoarrow.h
@@ -182,7 +182,16 @@ struct ArrowBufferAllocator ArrowBufferDeallocator(
 /// need to communicate more verbose error information accept a pointer
 /// to an ArrowError. This can be stack or statically allocated. The
 /// content of the message is undefined unless an error code has been
-/// returned.
+/// returned. If a nanoarrow function is passed a non-null ArrowError pointer, 
the
+/// ArrowError pointed to by the argument will be propagated with a
+/// null-terminated error message. It is safe to pass a NULL ArrowError 
anywhere
+/// in the nanoarrow API.
+///
+/// Except where documented, it is generally not safe to continue after a
+/// function has returned a non-zero ArrowErrorCode. The 
NANOARROW_RETURN_NOT_OK and
+/// NANOARROW_ASSERT_OK macros are provided to help propagate errors. C++ 
clients can use
+/// the helpers provided in the nanoarrow.hpp header to facilitate using C++ 
idioms
+/// for memory management and error propgagtion.
 ///
 /// @{
 
@@ -192,10 +201,24 @@ struct ArrowError {
   char message[1024];
 };
 
-/// \brief Set the contents of an error using printf syntax
+/// \brief Ensure an ArrowError is null-terminated by zeroing the first 
character.
+///
+/// If error is NULL, this function does nothing.
+static inline void ArrowErrorInit(struct ArrowError* error) {
+  if (error) {
+    error->message[0] = '\0';
+  }
+}
+
+/// \brief Set the contents of an error using printf syntax.
+///
+/// If error is NULL, this function does nothing and returns NANOARROW_OK.
 ArrowErrorCode ArrowErrorSet(struct ArrowError* error, const char* fmt, ...);
 
 /// \brief Get the contents of an error
+///
+/// If error is NULL, returns "", or returns the contents of the error message
+/// otherwise.
 const char* ArrowErrorMessage(struct ArrowError* error);
 
 /// @}
diff --git a/src/nanoarrow/nanoarrow.hpp b/src/nanoarrow/nanoarrow.hpp
index b01d2a6..e6adb3a 100644
--- a/src/nanoarrow/nanoarrow.hpp
+++ b/src/nanoarrow/nanoarrow.hpp
@@ -15,6 +15,7 @@
 // specific language governing permissions and limitations
 // under the License.
 
+#include <stdexcept>
 #include <vector>
 
 #include "nanoarrow.h"
@@ -31,6 +32,55 @@
 
 namespace nanoarrow {
 
+/// \defgroup nanoarrow_hpp-errors Error handling helpers
+///
+/// Most functions in the C API return an ArrowErrorCode to communicate
+/// possible failure. Except where documented, it is usually not safe to
+/// continue after a non-zero value has been returned. While the
+/// nanoarrow C++ helpers do not throw any exceptions of their own,
+/// these helpers are provided to facilitate using the nanoarrow C++ helpers
+/// in frameworks where this is a useful error handling idiom.
+///
+/// @{
+
+class Exception : public std::exception {
+ public:
+  Exception(const std::string& msg) : msg_(msg) {}
+  const char* what() const noexcept { return msg_.c_str(); }
+
+ private:
+  std::string msg_;
+};
+
+#if defined(NANOARROW_DEBUG)
+#define _NANOARROW_THROW_NOT_OK_IMPL(NAME, EXPR, EXPR_STR)                     
 \
+  do {                                                                         
 \
+    const int NAME = (EXPR);                                                   
 \
+    if (NAME) {                                                                
 \
+      throw nanoarrow::Exception(                                              
 \
+          std::string(EXPR_STR) + std::string(" failed with errno ") +         
 \
+          std::to_string(NAME) + std::string("\n * ") + std::string(__FILE__) 
+ \
+          std::string(":") + std::to_string(__LINE__) + std::string("\n"));    
 \
+    }                                                                          
 \
+  } while (0)
+#else
+#define _NANOARROW_THROW_NOT_OK_IMPL(NAME, EXPR, EXPR_STR)            \
+  do {                                                                \
+    const int NAME = (EXPR);                                          \
+    if (NAME) {                                                       \
+      throw nanoarrow::Exception(std::string(EXPR_STR) +              \
+                                 std::string(" failed with errno ") + \
+                                 std::to_string(NAME));               \
+    }                                                                 \
+  } while (0)
+#endif
+
+#define NANOARROW_THROW_NOT_OK(EXPR)                                           
        \
+  _NANOARROW_THROW_NOT_OK_IMPL(_NANOARROW_MAKE_NAME(errno_status_, 
__COUNTER__), EXPR, \
+                               #EXPR)
+
+/// @}
+
 namespace internal {
 
 /// \defgroup nanoarrow_hpp-unique_base Base classes for Unique wrappers
diff --git a/src/nanoarrow/nanoarrow_hpp_test.cc 
b/src/nanoarrow/nanoarrow_hpp_test.cc
index bd5393a..4b1d661 100644
--- a/src/nanoarrow/nanoarrow_hpp_test.cc
+++ b/src/nanoarrow/nanoarrow_hpp_test.cc
@@ -19,6 +19,16 @@
 
 #include "nanoarrow/nanoarrow.hpp"
 
+TEST(NanoarrowHppTest, NanoarrowHppExceptionTest) {
+  ASSERT_THROW(NANOARROW_THROW_NOT_OK(EINVAL), nanoarrow::Exception);
+  ASSERT_NO_THROW(NANOARROW_THROW_NOT_OK(NANOARROW_OK));
+  try {
+    NANOARROW_THROW_NOT_OK(EINVAL);
+  } catch (const nanoarrow::Exception& e) {
+    EXPECT_EQ(std::string(e.what()).substr(0, 24), "EINVAL failed with errno");
+  }
+}
+
 TEST(NanoarrowHppTest, NanoarrowHppUniqueArrayTest) {
   nanoarrow::UniqueArray array;
   EXPECT_EQ(array->release, nullptr);
diff --git a/src/nanoarrow/nanoarrow_types.h b/src/nanoarrow/nanoarrow_types.h
index ee380de..58582ab 100644
--- a/src/nanoarrow/nanoarrow_types.h
+++ b/src/nanoarrow/nanoarrow_types.h
@@ -23,6 +23,11 @@
 
 #include "nanoarrow_config.h"
 
+#if defined(NANOARROW_DEBUG) && !defined(NANOARROW_PRINT_AND_DIE)
+#include <stdio.h>
+#include <stdlib.h>
+#endif
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -159,6 +164,27 @@ static inline void ArrowArrayStreamMove(struct 
ArrowArrayStream* src,
 #define _NANOARROW_CHECK_RANGE(x_, min_, max_) \
   NANOARROW_RETURN_NOT_OK((x_ >= min_ && x_ <= max_) ? NANOARROW_OK : EINVAL)
 
+#if defined(NANOARROW_DEBUG)
+#define _NANOARROW_RETURN_NOT_OK_WITH_ERROR_IMPL(NAME, EXPR, ERROR_PTR_EXPR, 
EXPR_STR) \
+  do {                                                                         
        \
+    const int NAME = (EXPR);                                                   
        \
+    if (NAME) {                                                                
        \
+      ArrowErrorSet((ERROR_PTR_EXPR), "%s failed with errno %d\n* %s:%d", 
EXPR_STR,    \
+                    NAME, __FILE__, __LINE__);                                 
        \
+      return NAME;                                                             
        \
+    }                                                                          
        \
+  } while (0)
+#else
+#define _NANOARROW_RETURN_NOT_OK_WITH_ERROR_IMPL(NAME, EXPR, ERROR_PTR_EXPR, 
EXPR_STR) \
+  do {                                                                         
        \
+    const int NAME = (EXPR);                                                   
        \
+    if (NAME) {                                                                
        \
+      ArrowErrorSet((ERROR_PTR_EXPR), "%s failed with errno %d", EXPR_STR, 
NAME);      \
+      return NAME;                                                             
        \
+    }                                                                          
        \
+  } while (0)
+#endif
+
 /// \brief Return code for success.
 /// \ingroup nanoarrow-errors
 #define NANOARROW_OK 0
@@ -172,6 +198,47 @@ typedef int ArrowErrorCode;
 #define NANOARROW_RETURN_NOT_OK(EXPR) \
   _NANOARROW_RETURN_NOT_OK_IMPL(_NANOARROW_MAKE_NAME(errno_status_, 
__COUNTER__), EXPR)
 
+/// \brief Check the result of an expression and return it if not NANOARROW_OK,
+/// adding an auto-generated message to an ArrowError.
+/// \ingroup nanoarrow-errors
+///
+/// This macro is used to ensure that functions that accept an ArrowError
+/// as input always set its message when returning an error code (e.g., when 
calling
+/// a nanoarrow function that does *not* accept ArrowError).
+#define NANOARROW_RETURN_NOT_OK_WITH_ERROR(EXPR, ERROR_EXPR) \
+  _NANOARROW_RETURN_NOT_OK_WITH_ERROR_IMPL(                  \
+      _NANOARROW_MAKE_NAME(errno_status_, __COUNTER__), EXPR, ERROR_EXPR, 
#EXPR)
+
+#if defined(NANOARROW_DEBUG) && !defined(NANOARROW_PRINT_AND_DIE)
+#define NANOARROW_PRINT_AND_DIE(VALUE, EXPR_STR)                               
   \
+  do {                                                                         
   \
+    fprintf(stderr, "%s failed with errno %d\n* %s:%d\n", EXPR_STR, 
(int)(VALUE), \
+            __FILE__, (int)__LINE__);                                          
   \
+    abort();                                                                   
   \
+  } while (0)
+#endif
+
+#if defined(NANOARROW_DEBUG)
+#define _NANOARROW_ASSERT_OK_IMPL(NAME, EXPR, EXPR_STR) \
+  do {                                                  \
+    const int NAME = (EXPR);                            \
+    if (NAME) NANOARROW_PRINT_AND_DIE(NAME, EXPR_STR);  \
+  } while (0)
+
+/// \brief Assert that an expression's value is NANOARROW_OK
+/// \ingroup nanoarrow-errors
+///
+/// If nanoarrow was built in debug mode (i.e., defined(NANOARROW_DEBUG) is 
true),
+/// print a message to stderr and abort. If nanoarrow was bulit in release 
mode,
+/// this statement has no effect. You can customize fatal error behaviour
+/// be defining the NANOARROW_PRINT_AND_DIE macro before including nanoarrow.h
+/// This macro is provided as a convenience for users and is not used 
internally.
+#define NANOARROW_ASSERT_OK(EXPR) \
+  _NANOARROW_ASSERT_OK_IMPL(_NANOARROW_MAKE_NAME(errno_status_, __COUNTER__), 
EXPR, #EXPR)
+#else
+#define NANOARROW_ASSERT_OK(EXPR) EXPR
+#endif
+
 static char _ArrowIsLittleEndian(void) {
   uint32_t check = 1;
   char first_byte;
diff --git a/src/nanoarrow/utils.c b/src/nanoarrow/utils.c
index b87c1c3..b16fdb9 100644
--- a/src/nanoarrow/utils.c
+++ b/src/nanoarrow/utils.c
@@ -49,7 +49,13 @@ int ArrowErrorSet(struct ArrowError* error, const char* fmt, 
...) {
   }
 }
 
-const char* ArrowErrorMessage(struct ArrowError* error) { return 
error->message; }
+const char* ArrowErrorMessage(struct ArrowError* error) {
+  if (error == NULL) {
+    return "";
+  } else {
+    return error->message;
+  }
+}
 
 void ArrowLayoutInit(struct ArrowLayout* layout, enum ArrowType storage_type) {
   layout->buffer_type[0] = NANOARROW_BUFFER_TYPE_VALIDITY;
diff --git a/src/nanoarrow/utils_test.cc b/src/nanoarrow/utils_test.cc
index 74bb377..57d47eb 100644
--- a/src/nanoarrow/utils_test.cc
+++ b/src/nanoarrow/utils_test.cc
@@ -31,14 +31,23 @@ TEST(BuildIdTest, VersionTest) {
   EXPECT_EQ(ArrowNanoarrowVersionInt(), NANOARROW_VERSION_INT);
 }
 
+TEST(ErrorTest, ErrorTestInit) {
+  struct ArrowError error;
+  memset(&error.message, 0xff, sizeof(ArrowError));
+  ArrowErrorInit(&error);
+  EXPECT_STREQ(ArrowErrorMessage(&error), "");
+  ArrowErrorInit(nullptr);
+  EXPECT_STREQ(ArrowErrorMessage(nullptr), "");
+}
+
 TEST(ErrorTest, ErrorTestSet) {
-  ArrowError error;
+  struct ArrowError error;
   EXPECT_EQ(ArrowErrorSet(&error, "there were %d foxes", 4), NANOARROW_OK);
   EXPECT_STREQ(ArrowErrorMessage(&error), "there were 4 foxes");
 }
 
 TEST(ErrorTest, ErrorTestSetOverrun) {
-  ArrowError error;
+  struct ArrowError error;
   char big_error[2048];
   const char* a_few_chars = "abcdefg";
   for (int i = 0; i < 2047; i++) {
@@ -53,6 +62,22 @@ TEST(ErrorTest, ErrorTestSetOverrun) {
   EXPECT_EQ(ArrowErrorSet(&error, "%ls", bad_string), EINVAL);
 }
 
+#if defined(NANOARROW_DEBUG)
+#undef NANOARROW_PRINT_AND_DIE
+#define NANOARROW_PRINT_AND_DIE(VALUE, EXPR_STR)             \
+  do {                                                       \
+    ArrowErrorSet(&error, "%s failed with errno", EXPR_STR); \
+  } while (0)
+
+TEST(ErrorTest, ErrorTestAssertNotOkDebug) {
+  struct ArrowError error;
+  NANOARROW_ASSERT_OK(EINVAL);
+  EXPECT_STREQ(ArrowErrorMessage(&error), "EINVAL failed with errno");
+}
+#else
+TEST(ErrorTest, ErrorTestAssertNotOkRelease) { NANOARROW_ASSERT_OK(EINVAL); }
+#endif
+
 static uint8_t* MemoryPoolReallocate(struct ArrowBufferAllocator* allocator, 
uint8_t* ptr,
                                      int64_t old_size, int64_t new_size) {
   MemoryPool* pool = reinterpret_cast<MemoryPool*>(allocator->private_data);

Reply via email to