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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions src/iceberg/exception.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,13 @@

#include "iceberg/iceberg_export.h"

#define ICEBERG_CHECK_OR_DIE(condition, ...) \
do { \
if (!(condition)) [[unlikely]] { \
throw ::iceberg::IcebergError(std::format(__VA_ARGS__)); \
} \
} while (0)

namespace iceberg {

/// \brief Base exception class for exceptions thrown by the Iceberg library.
Expand All @@ -44,11 +51,4 @@ class ICEBERG_EXPORT ExpressionError : public IcebergError {
explicit ExpressionError(const std::string& what) : IcebergError(what) {}
};

#define ICEBERG_CHECK_OR_DIE(condition, ...) \
do { \
if (!(condition)) [[unlikely]] { \
throw iceberg::IcebergError(std::format(__VA_ARGS__)); \
} \
} while (0)

} // namespace iceberg
1 change: 1 addition & 0 deletions src/iceberg/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ add_iceberg_test(util_test
data_file_set_test.cc
decimal_test.cc
endian_test.cc
error_macros_test.cc
expected_test.cc
file_io_test.cc
formatter_test.cc
Expand Down
133 changes: 133 additions & 0 deletions src/iceberg/test/error_macros_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
/*
* 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 <gmock/gmock.h>
#include <gtest/gtest.h>

#include "iceberg/exception.h"
#include "iceberg/result.h"
#include "iceberg/test/matchers.h"
#include "iceberg/util/error_collector.h"
#include "iceberg/util/macros.h"

namespace {

using ::iceberg::HasErrorMessage;
using ::iceberg::HasValue;
using ::iceberg::IsError;
using ::iceberg::IsOk;

// The macros expand here, in a namespace with its own nested iceberg namespace, so a
// macro that names iceberg::X instead of ::iceberg::X fails to compile.
namespace caller {

namespace iceberg {}

::iceberg::Result<int> Parse(bool valid) {
if (!valid) {
return ::iceberg::InvalidArgument("Invalid input");
}
return 42;
}

::iceberg::Result<int> AddOne(bool valid) {
ICEBERG_ASSIGN_OR_RAISE(auto value, Parse(valid));
return value + 1;
}

::iceberg::Status Validate(bool valid) {
ICEBERG_RETURN_UNEXPECTED(Parse(valid));
return {};
}

::iceberg::Status CheckRange(int value) {
ICEBERG_PRECHECK(value > 0, "Value must be positive, got {}", value);
ICEBERG_CHECK(value < 100, "Value must be below 100, got {}", value);
return {};
}

int AddOneOrThrow(bool valid) {
ICEBERG_ASSIGN_OR_THROW(auto value, Parse(valid));
return value + 1;
}

int CheckPositiveOrDie(int value) {
ICEBERG_CHECK_OR_DIE(value > 0, "Value must be positive, got {}", value);
return value;
}

class TestBuilder : public ::iceberg::ErrorCollector {
public:
TestBuilder& Set(int value) {
ICEBERG_BUILDER_CHECK(value > 0, "Value must be positive, got {}", value);
ICEBERG_BUILDER_ASSIGN_OR_RETURN(value_, Parse(value < 100));
ICEBERG_BUILDER_ASSIGN_OR_RETURN_WITH_ERROR(value_, Parse(value < 50),
"Value {} is too large", value);
return *this;
}

private:
int value_ = 0;
};

} // namespace caller

using caller::AddOne;
using caller::AddOneOrThrow;
using caller::CheckPositiveOrDie;
using caller::CheckRange;
using caller::TestBuilder;
using caller::Validate;

} // namespace

TEST(ErrorMacrosTest, AssignOrRaise) {
EXPECT_THAT(AddOne(true), HasValue(::testing::Eq(43)));
EXPECT_THAT(AddOne(false), IsError(iceberg::ErrorKind::kInvalidArgument));
}

TEST(ErrorMacrosTest, ReturnUnexpected) {
EXPECT_THAT(Validate(true), IsOk());
EXPECT_THAT(Validate(false), HasErrorMessage("Invalid input"));
}

TEST(ErrorMacrosTest, PrecheckAndCheck) {
EXPECT_THAT(CheckRange(1), IsOk());
EXPECT_THAT(CheckRange(0), IsError(iceberg::ErrorKind::kInvalidArgument));
EXPECT_THAT(CheckRange(100), IsError(iceberg::ErrorKind::kValidationFailed));
}

TEST(ErrorMacrosTest, AssignOrThrow) {
EXPECT_EQ(AddOneOrThrow(true), 43);
EXPECT_THROW(AddOneOrThrow(false), iceberg::IcebergError);
}

TEST(ErrorMacrosTest, CheckOrDie) {
EXPECT_EQ(CheckPositiveOrDie(1), 1);
EXPECT_THROW(CheckPositiveOrDie(0), iceberg::IcebergError);
}

TEST(ErrorMacrosTest, BuilderMacros) {
EXPECT_THAT(TestBuilder().Set(1).CheckErrors(), IsOk());
EXPECT_THAT(TestBuilder().Set(0).CheckErrors(),
HasErrorMessage("Value must be positive, got 0"));
EXPECT_THAT(TestBuilder().Set(150).CheckErrors(), HasErrorMessage("Invalid input"));
EXPECT_THAT(TestBuilder().Set(70).CheckErrors(),
HasErrorMessage("Value 70 is too large"));
}
20 changes: 11 additions & 9 deletions src/iceberg/util/error_collector.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,8 @@
#include "iceberg/iceberg_export.h"
#include "iceberg/result.h"

namespace iceberg {

// The builder macros name Iceberg symbols with their full namespace, so builders outside
// namespace iceberg can use them.
#define ICEBERG_BUILDER_RETURN_IF_ERROR(result) \
if (auto&& result_name = result; !result_name) [[unlikely]] { \
errors_.emplace_back(std::move(result_name.error())); \
Expand All @@ -49,7 +49,7 @@ namespace iceberg {
#define ICEBERG_BUILDER_ASSIGN_OR_RETURN_WITH_ERROR_IMPL(result_name, lhs, rexpr, ...) \
auto&& result_name = (rexpr); \
if (!result_name) [[unlikely]] { \
AddError(ErrorKind::kInvalidArgument, __VA_ARGS__); \
AddError(::iceberg::ErrorKind::kInvalidArgument, __VA_ARGS__); \
return *this; \
} \
lhs = std::move(result_name.value());
Expand All @@ -58,14 +58,16 @@ namespace iceberg {
ICEBERG_BUILDER_ASSIGN_OR_RETURN_WITH_ERROR_IMPL( \
ICEBERG_ASSIGN_OR_RAISE_NAME(result_, __COUNTER__), lhs, rexpr, __VA_ARGS__)

#define ICEBERG_BUILDER_CHECK(expr, ...) \
do { \
if (!(expr)) [[unlikely]] { \
AddError(ErrorKind::kInvalidArgument, __VA_ARGS__); \
return *this; \
} \
#define ICEBERG_BUILDER_CHECK(expr, ...) \
do { \
if (!(expr)) [[unlikely]] { \
AddError(::iceberg::ErrorKind::kInvalidArgument, __VA_ARGS__); \
return *this; \
} \
} while (false)

namespace iceberg {

/// \brief Base class for collecting errors in the builder pattern.
///
/// This class equips builders with error accumulation capabilities to make it easy
Expand Down
39 changes: 21 additions & 18 deletions src/iceberg/util/macros.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,15 +21,18 @@

/// \file iceberg/util/macros.h
/// \brief Define common Iceberg utility macros.
///
/// The macros name Iceberg symbols with their full namespace, so code outside namespace
Comment thread
manuzhang marked this conversation as resolved.
/// iceberg can use them.

#include <cassert>

#include "iceberg/exception.h"
#include "iceberg/result.h"

#define ICEBERG_RETURN_UNEXPECTED(expr) \
if (auto&& result_name = expr; !result_name) [[unlikely]] { \
return iceberg::unexpected<Error>(result_name.error()); \
#define ICEBERG_RETURN_UNEXPECTED(expr) \
if (auto&& result_name = expr; !result_name) [[unlikely]] { \
return ::iceberg::unexpected<::iceberg::Error>(result_name.error()); \
}

#define ICEBERG_ASSIGN_OR_RAISE_IMPL(result_name, lhs, rexpr) \
Expand All @@ -49,26 +52,26 @@
#define ICEBERG_DCHECK(expr, message) assert((expr) && (message))

// Macro for precondition checks, usually used for function arguments
#define ICEBERG_PRECHECK(expr, ...) \
do { \
if (!(expr)) [[unlikely]] { \
return InvalidArgument(__VA_ARGS__); \
} \
#define ICEBERG_PRECHECK(expr, ...) \
do { \
if (!(expr)) [[unlikely]] { \
return ::iceberg::InvalidArgument(__VA_ARGS__); \
} \
} while (0)

// Macro for state checks, usually used for unexpected states
#define ICEBERG_CHECK(expr, ...) \
do { \
if (!(expr)) [[unlikely]] { \
return ValidationFailed(__VA_ARGS__); \
} \
#define ICEBERG_CHECK(expr, ...) \
do { \
if (!(expr)) [[unlikely]] { \
return ::iceberg::ValidationFailed(__VA_ARGS__); \
} \
} while (0)

#define ERROR_TO_EXCEPTION(error) \
if (error.kind == iceberg::ErrorKind::kInvalidExpression) { \
throw iceberg::ExpressionError(error.message); \
} else { \
throw iceberg::IcebergError(error.message); \
#define ERROR_TO_EXCEPTION(error) \
if (error.kind == ::iceberg::ErrorKind::kInvalidExpression) { \
throw ::iceberg::ExpressionError(error.message); \
} else { \
throw ::iceberg::IcebergError(error.message); \
}

#define ICEBERG_THROW_NOT_OK(expr) \
Expand Down
Loading