Skip to content

Commit 1d1602f

Browse files
committed
fix review
1 parent 9f15a10 commit 1d1602f

File tree

6 files changed

+61
-43
lines changed

6 files changed

+61
-43
lines changed

src/iceberg/catalog/rest/json_internal.cc

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
#include "iceberg/catalog/rest/json_internal.h"
2121

2222
#include <string>
23-
#include <unordered_map>
2423
#include <utility>
2524
#include <vector>
2625

@@ -75,9 +74,7 @@ nlohmann::json ToJson(const CatalogConfig& config) {
7574
nlohmann::json json;
7675
json[kOverrides] = config.overrides;
7776
json[kDefaults] = config.defaults;
78-
if (!config.endpoints.empty()) {
79-
json[kEndpoints] = config.endpoints;
80-
}
77+
SetContainerField(json, kEndpoints, config.endpoints);
8178
return json;
8279
}
8380

@@ -100,17 +97,18 @@ nlohmann::json ToJson(const ErrorModel& error) {
10097
json[kMessage] = error.message;
10198
json[kType] = error.type;
10299
json[kCode] = error.code;
103-
if (!error.stack.empty()) {
104-
json[kStack] = error.stack;
105-
}
100+
SetContainerField(json, kStack, error.stack);
106101
return json;
107102
}
108103

109104
Result<ErrorModel> ErrorModelFromJson(const nlohmann::json& json) {
110105
ErrorModel error;
106+
// NOTE: Iceberg's Java implementation allows missing required fields (message, type,
107+
// code) during deserialization, which deviates from the REST spec. We enforce strict
108+
// validation here.
111109
ICEBERG_ASSIGN_OR_RAISE(error.message, GetJsonValue<std::string>(json, kMessage));
112110
ICEBERG_ASSIGN_OR_RAISE(error.type, GetJsonValue<std::string>(json, kType));
113-
ICEBERG_ASSIGN_OR_RAISE(error.code, GetJsonValue<uint16_t>(json, kCode));
111+
ICEBERG_ASSIGN_OR_RAISE(error.code, GetJsonValue<uint32_t>(json, kCode));
114112
ICEBERG_ASSIGN_OR_RAISE(error.stack,
115113
GetJsonValueOrDefault<std::vector<std::string>>(json, kStack));
116114
ICEBERG_RETURN_UNEXPECTED(Validator::Validate(error));

src/iceberg/catalog/rest/types.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,16 @@ namespace iceberg::rest {
3535

3636
/// \brief Server-provided configuration for the catalog.
3737
struct ICEBERG_REST_EXPORT CatalogConfig {
38-
std::unordered_map<std::string, std::string> overrides; // required
3938
std::unordered_map<std::string, std::string> defaults; // required
39+
std::unordered_map<std::string, std::string> overrides; // required
4040
std::vector<std::string> endpoints;
4141
};
4242

4343
/// \brief JSON error payload returned in a response with further details on the error.
4444
struct ICEBERG_REST_EXPORT ErrorModel {
4545
std::string message; // required
4646
std::string type; // required
47-
uint16_t code; // required
47+
uint32_t code; // required
4848
std::vector<std::string> stack;
4949
};
5050

src/iceberg/catalog/rest/validator.cc

Lines changed: 41 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,12 @@
1919

2020
#include "iceberg/catalog/rest/validator.h"
2121

22+
#include <algorithm>
2223
#include <format>
23-
#include <ranges>
24-
#include <unordered_set>
25-
#include <utility>
2624

2725
#include "iceberg/catalog/rest/types.h"
2826
#include "iceberg/result.h"
27+
#include "iceberg/util/macros.h"
2928

3029
namespace iceberg::rest {
3130

@@ -40,18 +39,20 @@ Status Validator::Validate(const CatalogConfig& config) {
4039
}
4140

4241
Status Validator::Validate(const ErrorModel& error) {
43-
if (error.message.empty() || error.type.empty()) [[unlikely]] {
42+
if (error.message.empty() || error.type.empty()) {
4443
return Invalid("Invalid error model: missing required fields");
4544
}
4645

47-
if (error.code < 400 || error.code > 600) [[unlikely]] {
48-
return Invalid("Invalid error model: code must be between 400 and 600");
46+
if (error.code < 400 || error.code > 600) {
47+
return Invalid("Invalid error model: code {} is out of range [400, 600]", error.code);
4948
}
5049

5150
// stack is optional, no validation needed
5251
return {};
5352
}
5453

54+
// We don't validate the error field because ErrorModel::Validate has been called in the
55+
// FromJson.
5556
Status Validator::Validate(const ErrorResponse& response) { return {}; }
5657

5758
// Namespace operations
@@ -66,30 +67,38 @@ Status Validator::Validate(const GetNamespaceResponse& response) { return {}; }
6667

6768
Status Validator::Validate(const UpdateNamespacePropertiesRequest& request) {
6869
// keys in updates and removals must not overlap
69-
if (request.removals.empty() || request.updates.empty()) [[unlikely]] {
70+
if (request.removals.empty() || request.updates.empty()) {
7071
return {};
7172
}
7273

73-
std::unordered_set<std::string> remove_set(request.removals.begin(),
74-
request.removals.end());
75-
std::vector<std::string> common;
76-
77-
for (const std::string& k : request.updates | std::views::keys) {
78-
if (remove_set.contains(k)) {
79-
common.push_back(k);
74+
auto extract_and_sort = [](const auto& container, auto key_extractor) {
75+
std::vector<std::string_view> result;
76+
result.reserve(container.size());
77+
for (const auto& item : container) {
78+
result.push_back(std::string_view{key_extractor(item)});
8079
}
81-
}
80+
std::ranges::sort(result);
81+
return result;
82+
};
83+
84+
auto sorted_removals =
85+
extract_and_sort(request.removals, [](const auto& s) -> const auto& { return s; });
86+
auto sorted_update_keys = extract_and_sort(
87+
request.updates, [](const auto& pair) -> const auto& { return pair.first; });
88+
89+
std::vector<std::string_view> common;
90+
std::ranges::set_intersection(sorted_removals, sorted_update_keys,
91+
std::back_inserter(common));
8292

8393
if (!common.empty()) {
8494
std::string keys;
85-
bool first = true;
86-
for (const std::string& s : common) {
87-
if (!std::exchange(first, false)) keys += ", ";
88-
keys += s;
95+
for (size_t i = 0; i < common.size(); ++i) {
96+
if (i) keys += ", ";
97+
keys += common[i];
8998
}
9099

91100
return Invalid(
92-
"Invalid namespace properties update: cannot simultaneously set and remove keys: "
101+
"Invalid namespace update: cannot simultaneously set and remove keys: "
93102
"[{}]",
94103
keys);
95104
}
@@ -105,34 +114,34 @@ Status Validator::Validate(const UpdateNamespacePropertiesResponse& response) {
105114
Status Validator::Validate(const ListTablesResponse& response) { return {}; }
106115

107116
Status Validator::Validate(const LoadTableResult& result) {
108-
if (!result.metadata) [[unlikely]] {
117+
if (!result.metadata) {
109118
return Invalid("Invalid metadata: null");
110119
}
111120
return {};
112121
}
113122

114123
Status Validator::Validate(const RegisterTableRequest& request) {
115-
if (request.name.empty()) [[unlikely]] {
116-
return Invalid("Invalid table name: empty");
124+
if (request.name.empty()) {
125+
return Invalid("Missing table name");
117126
}
118127

119-
if (request.metadata_location.empty()) [[unlikely]] {
120-
return Invalid("Invalid metadata location: empty");
128+
if (request.metadata_location.empty()) {
129+
return Invalid("Empty metadata location");
121130
}
122131

123132
return {};
124133
}
125134

126135
Status Validator::Validate(const RenameTableRequest& request) {
127-
if (request.source.ns.levels.empty() || request.source.name.empty()) [[unlikely]] {
128-
return Invalid("Invalid source identifier");
129-
}
136+
ICEBERG_RETURN_UNEXPECTED(Validate(request.source));
137+
ICEBERG_RETURN_UNEXPECTED(Validate(request.destination));
138+
return {};
139+
}
130140

131-
if (request.destination.ns.levels.empty() || request.destination.name.empty())
132-
[[unlikely]] {
133-
return Invalid("Invalid destination identifier");
141+
Status Validator::Validate(const TableIdentifier& identifier) {
142+
if (identifier.name.empty()) {
143+
return Invalid("Invalid table identifier: missing table name");
134144
}
135-
136145
return {};
137146
}
138147

src/iceberg/catalog/rest/validator.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ class ICEBERG_REST_EXPORT Validator {
7777

7878
/// \brief Validates a RenameTableRequest object.
7979
static Status Validate(const RenameTableRequest& request);
80+
81+
// Other types
82+
83+
/// \brief Validates a TableIdentifier object.
84+
static Status Validate(const TableIdentifier& identifier);
8085
};
8186

8287
} // namespace iceberg::rest

src/iceberg/test/meson.build

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,12 @@ if get_option('rest').enabled()
9696
}
9797
endif
9898

99+
cpp = meson.get_compiler('cpp')
100+
cpp_bigobj_args = []
101+
if cpp.get_id() == 'msvc'
102+
iceberg_common_cpp_args += cpp.get_supported_arguments(['/bigobj'])
103+
endif
104+
99105
foreach test_name, values : iceberg_tests
100106
exc = executable(
101107
test_name,
@@ -104,6 +110,7 @@ foreach test_name, values : iceberg_tests
104110
'dependencies',
105111
[],
106112
),
113+
cpp_args: cpp_bigobj_args,
107114
)
108115
test(test_name, exc)
109116
endforeach

src/iceberg/test/rest_json_internal_test.cc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -940,7 +940,6 @@ INSTANTIATE_TEST_SUITE_P(
940940
return info.param.test_name;
941941
});
942942

943-
// ErrorModel tests - testing the nested error model structure
944943
DECLARE_INVALID_TEST(ErrorModel)
945944

946945
INSTANTIATE_TEST_SUITE_P(

0 commit comments

Comments
 (0)