Skip to content

Commit 674c8ab

Browse files
committed
update
1 parent 6974124 commit 674c8ab

18 files changed

+539
-249
lines changed

src/iceberg/catalog/rest/CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ set(ICEBERG_REST_SOURCES
1919
catalog.cc
2020
json_internal.cc
2121
config.cc
22-
http_client_internal.cc
22+
http_client.cc
2323
resource_paths.cc)
2424

2525
set(ICEBERG_REST_STATIC_BUILD_INTERFACE_LIBS)

src/iceberg/catalog/rest/catalog.cc

Lines changed: 38 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -20,14 +20,18 @@
2020
#include "iceberg/catalog/rest/catalog.h"
2121

2222
#include <memory>
23+
#include <unordered_map>
2324
#include <utility>
2425

2526
#include <cpr/cpr.h>
2627

28+
#include "iceberg/catalog/rest/catalog.h"
2729
#include "iceberg/catalog/rest/config.h"
2830
#include "iceberg/catalog/rest/constant.h"
2931
#include "iceberg/catalog/rest/endpoint_util.h"
30-
#include "iceberg/catalog/rest/http_client_interal.h"
32+
#include "iceberg/catalog/rest/error_handlers.h"
33+
#include "iceberg/catalog/rest/http_client.h"
34+
#include "iceberg/catalog/rest/http_response.h"
3135
#include "iceberg/catalog/rest/json_internal.h"
3236
#include "iceberg/catalog/rest/types.h"
3337
#include "iceberg/json_internal.h"
@@ -41,37 +45,27 @@ Result<std::unique_ptr<RestCatalog>> RestCatalog::Make(const RestCatalogConfig&
4145
// Create ResourcePaths and validate URI
4246
ICEBERG_ASSIGN_OR_RAISE(auto paths, ResourcePaths::Make(config));
4347

44-
ICEBERG_ASSIGN_OR_RAISE(auto tmp_client, HttpClient::Make(config));
45-
48+
auto tmp_client = std::make_unique<HttpClient>(config);
4649
const std::string endpoint = paths->V1Config();
47-
cpr::Parameters params;
48-
ICEBERG_ASSIGN_OR_RAISE(const auto& response, tmp_client->Get(endpoint, params));
49-
switch (response.status_code) {
50-
case cpr::status::HTTP_OK: {
51-
ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.text));
52-
ICEBERG_ASSIGN_OR_RAISE(auto server_config, CatalogConfigFromJson(json));
53-
// Merge server config into client config, server config overrides > client config
54-
// properties > server config defaults
55-
auto final_props = std::move(server_config.defaults);
56-
for (const auto& kv : config.configs()) {
57-
final_props.insert_or_assign(kv.first, kv.second);
58-
}
59-
60-
for (const auto& kv : server_config.overrides) {
61-
final_props.insert_or_assign(kv.first, kv.second);
62-
}
63-
auto final_config = RestCatalogConfig::FromMap(final_props);
64-
ICEBERG_ASSIGN_OR_RAISE(auto client, HttpClient::Make(*final_config));
65-
ICEBERG_ASSIGN_OR_RAISE(auto final_paths, ResourcePaths::Make(*final_config));
66-
return std::unique_ptr<RestCatalog>(new RestCatalog(
67-
std::move(final_config), std::move(client), std::move(*final_paths)));
68-
};
69-
default: {
70-
ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.text));
71-
ICEBERG_ASSIGN_OR_RAISE(auto list_response, ErrorResponseFromJson(json));
72-
return UnknownError("Error listing namespaces: {}", list_response.error.message);
73-
}
50+
ICEBERG_ASSIGN_OR_RAISE(const HttpResponse& response,
51+
tmp_client->Get(endpoint, {}, {}, DefaultErrorHandler()));
52+
ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body()));
53+
ICEBERG_ASSIGN_OR_RAISE(auto server_config, CatalogConfigFromJson(json));
54+
// Merge server config into client config, server config overrides > client config
55+
// properties > server config defaults
56+
auto final_props = std::move(server_config.defaults);
57+
for (const auto& kv : config.configs()) {
58+
final_props.insert_or_assign(kv.first, kv.second);
59+
}
60+
61+
for (const auto& kv : server_config.overrides) {
62+
final_props.insert_or_assign(kv.first, kv.second);
7463
}
64+
auto final_config = RestCatalogConfig::FromMap(final_props);
65+
auto client = std::make_unique<HttpClient>(*final_config);
66+
ICEBERG_ASSIGN_OR_RAISE(auto final_paths, ResourcePaths::Make(*final_config));
67+
return std::unique_ptr<RestCatalog>(new RestCatalog(
68+
std::move(final_config), std::move(client), std::move(*final_paths)));
7569
}
7670

7771
RestCatalog::RestCatalog(std::unique_ptr<RestCatalogConfig> config,
@@ -91,35 +85,26 @@ Result<std::vector<Namespace>> RestCatalog::ListNamespaces(const Namespace& ns)
9185
std::vector<Namespace> result;
9286
std::string next_token;
9387
while (true) {
94-
cpr::Parameters params;
88+
std::unordered_map<std::string, std::string> params;
9589
if (!ns.levels.empty()) {
96-
params.Add({std::string(kQueryParamParent), EncodeNamespaceForUrl(ns)});
90+
params[kQueryParamParent] = EncodeNamespaceForUrl(ns);
9791
}
9892
if (!next_token.empty()) {
99-
params.Add({std::string(kQueryParamPageToken), next_token});
93+
params[kQueryParamPageToken] = next_token;
10094
}
101-
ICEBERG_ASSIGN_OR_RAISE(const auto& response, client_->Get(endpoint, params));
102-
switch (response.status_code) {
103-
case cpr::status::HTTP_OK: {
104-
ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.text));
105-
ICEBERG_ASSIGN_OR_RAISE(auto list_response, ListNamespacesResponseFromJson(json));
106-
result.insert(result.end(), list_response.namespaces.begin(),
107-
list_response.namespaces.end());
108-
if (list_response.next_page_token.empty()) {
109-
return result;
110-
}
111-
next_token = list_response.next_page_token;
112-
continue;
113-
}
114-
case cpr::status::HTTP_NOT_FOUND: {
115-
return NoSuchNamespace("Namespace not found");
116-
}
117-
default:
118-
ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.text));
119-
ICEBERG_ASSIGN_OR_RAISE(auto list_response, ErrorResponseFromJson(json));
120-
return UnknownError("Error listing namespaces: {}", list_response.error.message);
95+
ICEBERG_ASSIGN_OR_RAISE(const auto& response,
96+
client_->Get(endpoint, params, {}, NamespaceErrorHandler()));
97+
ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body()));
98+
ICEBERG_ASSIGN_OR_RAISE(auto list_response, ListNamespacesResponseFromJson(json));
99+
result.insert(result.end(), list_response.namespaces.begin(),
100+
list_response.namespaces.end());
101+
if (list_response.next_page_token.empty()) {
102+
return result;
121103
}
104+
next_token = list_response.next_page_token;
105+
continue;
122106
}
107+
return result;
123108
}
124109

125110
Status RestCatalog::CreateNamespace(

src/iceberg/catalog/rest/catalog.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424

2525
#include "iceberg/catalog.h"
2626
#include "iceberg/catalog/rest/config.h"
27-
#include "iceberg/catalog/rest/http_client_interal.h"
27+
#include "iceberg/catalog/rest/http_client.h"
2828
#include "iceberg/catalog/rest/iceberg_rest_export.h"
2929
#include "iceberg/catalog/rest/resource_paths.h"
3030
#include "iceberg/result.h"

src/iceberg/catalog/rest/config.cc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
namespace iceberg::rest {
2525

26-
std::unique_ptr<RestCatalogConfig> RestCatalogConfig::default_properties() {
26+
std::unique_ptr<RestCatalogConfig> RestCatalogConfig::DefaultProperties() {
2727
return std::make_unique<RestCatalogConfig>();
2828
}
2929

src/iceberg/catalog/rest/config.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class ICEBERG_REST_EXPORT RestCatalogConfig : public ConfigBase<RestCatalogConfi
5050
inline static std::string_view kWarehouse{"warehouse"};
5151

5252
/// \brief Create a default RestCatalogConfig instance.
53-
static std::unique_ptr<RestCatalogConfig> default_properties();
53+
static std::unique_ptr<RestCatalogConfig> DefaultProperties();
5454

5555
/// \brief Create a RestCatalogConfig instance from a map of key-value pairs.
5656
static std::unique_ptr<RestCatalogConfig> FromMap(

src/iceberg/catalog/rest/endpoint_util.h

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,11 @@ namespace iceberg::rest {
3131
/// \brief Trim a single trailing slash from a URI string_view.
3232
/// \details If \p uri_sv ends with '/', remove that last character; otherwise the input
3333
/// is returned unchanged.
34-
/// \param uri_sv The URI string view to trim.
35-
/// \return The (possibly) trimmed URI string view.
36-
inline std::string_view TrimTrailingSlash(std::string_view uri_sv) {
37-
if (uri_sv.ends_with('/')) {
38-
uri_sv.remove_suffix(1);
34+
/// \param uri_sv The URI string to trim.
35+
/// \return The (possibly) trimmed URI string.
36+
inline std::string TrimTrailingSlash(std::string uri_sv) {
37+
if (!uri_sv.empty() && uri_sv.back() == '/') {
38+
uri_sv.pop_back();
3939
}
4040
return uri_sv;
4141
}
Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
#pragma once
21+
22+
#include <format>
23+
#include <string>
24+
25+
#include "iceberg/catalog/rest/iceberg_rest_export.h"
26+
#include "iceberg/catalog/rest/types.h"
27+
#include "iceberg/result.h"
28+
29+
/// \file iceberg/catalog/rest/error_handlers.h
30+
/// Error handlers for different HTTP error types in Iceberg REST API.
31+
32+
namespace iceberg::rest {
33+
34+
/// \brief Error handler interface for processing REST API error responses. Maps HTTP
35+
/// status codes to appropriate ErrorKind values following the Iceberg REST specification.
36+
class ICEBERG_REST_EXPORT ErrorHandler {
37+
public:
38+
virtual ~ErrorHandler() = default;
39+
40+
/// \brief Process an error response and return an appropriate Error.
41+
///
42+
/// \param error The error model parsed from the HTTP response body
43+
/// \return An Error object with appropriate ErrorKind and message
44+
virtual Status Accept(const ErrorModel& error) const = 0;
45+
};
46+
47+
/// \brief Default error handler for REST API responses.
48+
class ICEBERG_REST_EXPORT DefaultErrorHandler : public ErrorHandler {
49+
public:
50+
Status Accept(const ErrorModel& error) const override {
51+
switch (error.code) {
52+
case 400:
53+
if (error.type == "IllegalArgumentException") {
54+
return InvalidArgument("{}", error.message);
55+
}
56+
return BadRequest("Malformed request: {}", error.message);
57+
58+
case 401:
59+
return NotAuthorized("Not authorized: {}", error.message);
60+
61+
case 403:
62+
return Forbidden("Forbidden: {}", error.message);
63+
64+
case 405:
65+
case 406:
66+
break;
67+
68+
case 500:
69+
return ServiceFailure("Server error: {}: {}", error.type, error.message);
70+
71+
case 501:
72+
return NotSupported("{}", error.message);
73+
74+
case 503:
75+
return ServiceUnavailable("Service unavailable: {}", error.message);
76+
}
77+
return RESTError("Unable to process: {}", error.message);
78+
}
79+
};
80+
81+
/// \brief Namespace-specific error handler for create/read/update operations.
82+
class ICEBERG_REST_EXPORT NamespaceErrorHandler : public DefaultErrorHandler {
83+
public:
84+
Status Accept(const ErrorModel& error) const override {
85+
switch (error.code) {
86+
case 400:
87+
if (error.type == "NamespaceNotEmptyException") {
88+
return NamespaceNotEmpty("{}", error.message);
89+
}
90+
return BadRequest("Malformed request: {}", error.message);
91+
92+
case 404:
93+
return NoSuchNamespace("{}", error.message);
94+
95+
case 409:
96+
return AlreadyExists("{}", error.message);
97+
98+
case 422:
99+
return RESTError("Unable to process: {}", error.message);
100+
}
101+
102+
return DefaultErrorHandler::Accept(error);
103+
}
104+
};
105+
106+
/// \brief Error handler for drop namespace operations.
107+
class ICEBERG_REST_EXPORT DropNamespaceErrorHandler : public NamespaceErrorHandler {
108+
public:
109+
Status Accept(const ErrorModel& error) const override {
110+
if (error.code == 409) {
111+
return NamespaceNotEmpty("{}", error.message);
112+
}
113+
114+
// Delegate to parent handler
115+
return NamespaceErrorHandler::Accept(error);
116+
}
117+
};
118+
119+
/// \brief Table-level error handler.
120+
class ICEBERG_REST_EXPORT TableErrorHandler : public DefaultErrorHandler {
121+
public:
122+
Status Accept(const ErrorModel& error) const override {
123+
switch (error.code) {
124+
case 404:
125+
if (error.type == "NoSuchNamespaceException") {
126+
return NoSuchNamespace("{}", error.message);
127+
}
128+
return NoSuchTable("{}", error.message);
129+
130+
case 409:
131+
return AlreadyExists("{}", error.message);
132+
}
133+
134+
return DefaultErrorHandler::Accept(error);
135+
}
136+
};
137+
138+
/// \brief View-level error handler.
139+
///
140+
/// Handles view-specific errors including NoSuchView, NoSuchNamespace,
141+
/// and AlreadyExists scenarios.
142+
class ICEBERG_REST_EXPORT ViewErrorHandler : public DefaultErrorHandler {
143+
public:
144+
Status Accept(const ErrorModel& error) const override {
145+
switch (error.code) {
146+
case 404:
147+
if (error.type == "NoSuchNamespaceException") {
148+
return NoSuchNamespace("{}", error.message);
149+
}
150+
return NoSuchView("{}", error.message);
151+
152+
case 409:
153+
return AlreadyExists("{}", error.message);
154+
}
155+
156+
return DefaultErrorHandler::Accept(error);
157+
}
158+
};
159+
160+
/// \brief Table commit operation error handler.
161+
class ICEBERG_REST_EXPORT TableCommitErrorHandler : public DefaultErrorHandler {
162+
public:
163+
Status Accept(const ErrorModel& error) const override {
164+
switch (error.code) {
165+
case 404:
166+
return NoSuchTable("{}", error.message);
167+
168+
case 409:
169+
return CommitFailed("Commit failed: {}", error.message);
170+
171+
case 500:
172+
case 502:
173+
case 503:
174+
case 504:
175+
return CommitStateUnknown("Service failed: {}: {}", error.code, error.message);
176+
}
177+
178+
return DefaultErrorHandler::Accept(error);
179+
}
180+
};
181+
182+
/// \brief View commit operation error handler.
183+
class ICEBERG_REST_EXPORT ViewCommitErrorHandler : public DefaultErrorHandler {
184+
public:
185+
Status Accept(const ErrorModel& error) const override {
186+
switch (error.code) {
187+
case 404:
188+
return NoSuchView("{}", error.message);
189+
190+
case 409:
191+
return CommitFailed("Commit failed: {}", error.message);
192+
193+
case 500:
194+
case 502:
195+
case 503:
196+
case 504:
197+
return CommitStateUnknown("Service failed: {}: {}", error.code, error.message);
198+
}
199+
200+
return DefaultErrorHandler::Accept(error);
201+
}
202+
};
203+
204+
} // namespace iceberg::rest

0 commit comments

Comments
 (0)