Skip to content

Commit ce743cc

Browse files
committed
feat: Implement server-provided endpoints configuration for REST
1 parent 898d862 commit ce743cc

16 files changed

Lines changed: 751 additions & 53 deletions

src/iceberg/catalog/rest/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
set(ICEBERG_REST_SOURCES
1919
rest_catalog.cc
2020
catalog_properties.cc
21+
endpoint.cc
2122
error_handlers.cc
2223
http_client.cc
2324
json_internal.cc
Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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+
#include "iceberg/catalog/rest/endpoint.h"
21+
22+
#include <format>
23+
24+
namespace iceberg::rest {
25+
26+
constexpr std::string_view ToString(HttpMethod method) {
27+
switch (method) {
28+
case HttpMethod::GET:
29+
return "GET";
30+
case HttpMethod::POST:
31+
return "POST";
32+
case HttpMethod::PUT:
33+
return "PUT";
34+
case HttpMethod::DELETE:
35+
return "DELETE";
36+
case HttpMethod::HEAD:
37+
return "HEAD";
38+
}
39+
return "UNKNOWN";
40+
}
41+
42+
Result<Endpoint> Endpoint::Create(HttpMethod method, std::string path_template) {
43+
if (path_template.empty()) {
44+
return InvalidArgument("Path template cannot be empty");
45+
}
46+
return Endpoint(method, path_template);
47+
}
48+
49+
Result<Endpoint> Endpoint::FromString(std::string_view str) {
50+
auto space_pos = str.find(' ');
51+
if (space_pos == std::string_view::npos ||
52+
str.find(' ', space_pos + 1) != std::string_view::npos) {
53+
return InvalidArgument(
54+
"Invalid endpoint format (must consist of two elements separated by a single "
55+
"space): {}",
56+
str);
57+
}
58+
59+
auto method_str = str.substr(0, space_pos);
60+
auto path_str = str.substr(space_pos + 1);
61+
62+
if (path_str.empty()) {
63+
return InvalidArgument("Invalid endpoint format: path is empty");
64+
}
65+
66+
// Parse HTTP method
67+
HttpMethod method;
68+
if (method_str == "GET") {
69+
method = HttpMethod::GET;
70+
} else if (method_str == "POST") {
71+
method = HttpMethod::POST;
72+
} else if (method_str == "PUT") {
73+
method = HttpMethod::PUT;
74+
} else if (method_str == "DELETE") {
75+
method = HttpMethod::DELETE;
76+
} else if (method_str == "HEAD") {
77+
method = HttpMethod::HEAD;
78+
} else {
79+
return InvalidArgument("Invalid HTTP method: '{}'", method_str);
80+
}
81+
82+
return Create(method, std::string(path_str));
83+
}
84+
85+
std::string Endpoint::ToString() const {
86+
return std::format("{} {}", rest::ToString(method_), path_template_);
87+
}
88+
89+
} // namespace iceberg::rest
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
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 <set>
23+
#include <string>
24+
#include <string_view>
25+
26+
#include "iceberg/catalog/rest/iceberg_rest_export.h"
27+
#include "iceberg/result.h"
28+
29+
/// \file iceberg/catalog/rest/endpoint.h
30+
/// Endpoint definitions for Iceberg REST API operations.
31+
32+
namespace iceberg::rest {
33+
34+
/// \brief HTTP method enumeration.
35+
enum class HttpMethod : uint8_t { GET, POST, PUT, DELETE, HEAD };
36+
37+
/// \brief Convert HttpMethod to string representation.
38+
constexpr std::string_view ToString(HttpMethod method);
39+
40+
/// \brief An Endpoint is an immutable value object identifying a specific REST API
41+
/// operation. It consists of:
42+
/// - HTTP method (GET, POST, DELETE, etc.)
43+
/// - Path template (e.g., "/v1/{prefix}/namespaces/{namespace}")
44+
class ICEBERG_REST_EXPORT Endpoint {
45+
public:
46+
/// \brief Create an endpoint with method and path template.
47+
///
48+
/// \param method HTTP method (GET, POST, etc.)
49+
/// \param path_template Path template with placeholders (e.g., "/v1/{prefix}/tables")
50+
/// \return Endpoint instance or error if invalid
51+
static Result<Endpoint> Create(HttpMethod method, std::string path_template);
52+
53+
/// \brief Parse endpoint from string representation.
54+
///
55+
/// \param str String in format "METHOD /path/template" (e.g., "GET /v1/namespaces")
56+
/// \return Endpoint instance or error if malformed
57+
static Result<Endpoint> FromString(std::string_view str);
58+
59+
/// \brief Get the HTTP method.
60+
constexpr HttpMethod method() const { return method_; }
61+
62+
/// \brief Get the path template.
63+
std::string_view path_template() const { return path_template_; }
64+
65+
/// \brief Serialize to "METHOD /path" format.
66+
std::string ToString() const;
67+
68+
/// \brief Equality comparison operator.
69+
constexpr bool operator==(const Endpoint& other) const {
70+
return method_ == other.method_ && path_template_ == other.path_template_;
71+
}
72+
73+
/// \brief Three-way comparison operator (C++20).
74+
constexpr auto operator<=>(const Endpoint& other) const {
75+
if (auto cmp = method_ <=> other.method_; cmp != 0) {
76+
return cmp;
77+
}
78+
return path_template_ <=> other.path_template_;
79+
}
80+
81+
// Namespace endpoints
82+
static constexpr Endpoint ListNamespaces() {
83+
return {HttpMethod::GET, "/v1/{prefix}/namespaces"};
84+
}
85+
static constexpr Endpoint GetNamespaceProperties() {
86+
return {HttpMethod::GET, "/v1/{prefix}/namespaces/{namespace}"};
87+
}
88+
static constexpr Endpoint NamespaceExists() {
89+
return {HttpMethod::HEAD, "/v1/{prefix}/namespaces/{namespace}"};
90+
}
91+
static constexpr Endpoint CreateNamespace() {
92+
return {HttpMethod::POST, "/v1/{prefix}/namespaces"};
93+
}
94+
static constexpr Endpoint UpdateNamespace() {
95+
return {HttpMethod::POST, "/v1/{prefix}/namespaces/{namespace}/properties"};
96+
}
97+
static constexpr Endpoint DropNamespace() {
98+
return {HttpMethod::DELETE, "/v1/{prefix}/namespaces/{namespace}"};
99+
}
100+
101+
// Table endpoints
102+
static constexpr Endpoint ListTables() {
103+
return {HttpMethod::GET, "/v1/{prefix}/namespaces/{namespace}/tables"};
104+
}
105+
static constexpr Endpoint LoadTable() {
106+
return {HttpMethod::GET, "/v1/{prefix}/namespaces/{namespace}/tables/{table}"};
107+
}
108+
static constexpr Endpoint TableExists() {
109+
return {HttpMethod::HEAD, "/v1/{prefix}/namespaces/{namespace}/tables/{table}"};
110+
}
111+
static constexpr Endpoint CreateTable() {
112+
return {HttpMethod::POST, "/v1/{prefix}/namespaces/{namespace}/tables"};
113+
}
114+
static constexpr Endpoint UpdateTable() {
115+
return {HttpMethod::POST, "/v1/{prefix}/namespaces/{namespace}/tables/{table}"};
116+
}
117+
static constexpr Endpoint DeleteTable() {
118+
return {HttpMethod::DELETE, "/v1/{prefix}/namespaces/{namespace}/tables/{table}"};
119+
}
120+
static constexpr Endpoint RenameTable() {
121+
return {HttpMethod::POST, "/v1/{prefix}/tables/rename"};
122+
}
123+
static constexpr Endpoint RegisterTable() {
124+
return {HttpMethod::POST, "/v1/{prefix}/namespaces/{namespace}/register"};
125+
}
126+
static constexpr Endpoint ReportMetrics() {
127+
return {HttpMethod::POST,
128+
"/v1/{prefix}/namespaces/{namespace}/tables/{table}/metrics"};
129+
}
130+
static constexpr Endpoint TableCredentials() {
131+
return {HttpMethod::GET,
132+
"/v1/{prefix}/namespaces/{namespace}/tables/{table}/credentials"};
133+
}
134+
135+
// Transaction endpoints
136+
static constexpr Endpoint CommitTransaction() {
137+
return {HttpMethod::POST, "/v1/{prefix}/transactions/commit"};
138+
}
139+
140+
private:
141+
Endpoint(HttpMethod method, std::string_view path_template)
142+
: method_(method), path_template_(path_template) {}
143+
144+
HttpMethod method_;
145+
std::string path_template_;
146+
};
147+
148+
} // namespace iceberg::rest

src/iceberg/catalog/rest/json_internal.cc

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,10 @@ nlohmann::json ToJson(const CatalogConfig& config) {
7373
nlohmann::json json;
7474
json[kOverrides] = config.overrides;
7575
json[kDefaults] = config.defaults;
76-
SetContainerField(json, kEndpoints, config.endpoints);
76+
for (const auto& endpoint : config.endpoints) {
77+
auto endpoint_str = endpoint.ToString();
78+
json[kEndpoints].push_back(endpoint_str);
79+
}
7780
return json;
7881
}
7982

@@ -85,8 +88,15 @@ Result<CatalogConfig> CatalogConfigFromJson(const nlohmann::json& json) {
8588
ICEBERG_ASSIGN_OR_RAISE(
8689
config.defaults, GetJsonValueOrDefault<decltype(config.defaults)>(json, kDefaults));
8790
ICEBERG_ASSIGN_OR_RAISE(
88-
config.endpoints,
89-
GetJsonValueOrDefault<std::vector<std::string>>(json, kEndpoints));
91+
auto endpoints, GetJsonValueOrDefault<std::vector<std::string>>(json, kEndpoints));
92+
for (const auto& endpoint_str : endpoints) {
93+
auto endpoint_result = Endpoint::FromString(endpoint_str);
94+
if (!endpoint_result.has_value()) {
95+
// Convert to JsonParseError in JSON deserialization context
96+
return JsonParseError("{}", endpoint_result.error().message);
97+
}
98+
config.endpoints.push_back(std::move(*endpoint_result));
99+
}
90100
ICEBERG_RETURN_UNEXPECTED(config.Validate());
91101
return config;
92102
}

src/iceberg/catalog/rest/meson.build

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
iceberg_rest_sources = files(
1919
'catalog_properties.cc',
20+
'endpoint.cc',
2021
'error_handlers.cc',
2122
'http_client.cc',
2223
'json_internal.cc',
@@ -58,6 +59,7 @@ install_headers(
5859
[
5960
'catalog_properties.h',
6061
'constant.h',
62+
'endpoint.h',
6163
'error_handlers.h',
6264
'http_client.h',
6365
'iceberg_rest_export.h',

0 commit comments

Comments
 (0)