Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
2 changes: 1 addition & 1 deletion src/iceberg/catalog/rest/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
# specific language governing permissions and limitations
# under the License.

set(ICEBERG_REST_SOURCES rest_catalog.cc json_internal.cc validator.cc)
set(ICEBERG_REST_SOURCES rest_catalog.cc json_internal.cc)

set(ICEBERG_REST_STATIC_BUILD_INTERFACE_LIBS)
set(ICEBERG_REST_SHARED_BUILD_INTERFACE_LIBS)
Expand Down
21 changes: 10 additions & 11 deletions src/iceberg/catalog/rest/json_internal.cc
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
#include <nlohmann/json.hpp>

#include "iceberg/catalog/rest/types.h"
#include "iceberg/catalog/rest/validator.h"
#include "iceberg/json_internal.h"
#include "iceberg/table_identifier.h"
#include "iceberg/util/json_util_internal.h"
Expand Down Expand Up @@ -88,7 +87,7 @@ Result<CatalogConfig> CatalogConfigFromJson(const nlohmann::json& json) {
ICEBERG_ASSIGN_OR_RAISE(
config.endpoints,
GetJsonValueOrDefault<std::vector<std::string>>(json, kEndpoints));
ICEBERG_RETURN_UNEXPECTED(Validator::Validate(config));
ICEBERG_RETURN_UNEXPECTED(config.Validate());
return config;
}

Expand All @@ -111,7 +110,7 @@ Result<ErrorModel> ErrorModelFromJson(const nlohmann::json& json) {
ICEBERG_ASSIGN_OR_RAISE(error.code, GetJsonValue<uint32_t>(json, kCode));
ICEBERG_ASSIGN_OR_RAISE(error.stack,
GetJsonValueOrDefault<std::vector<std::string>>(json, kStack));
ICEBERG_RETURN_UNEXPECTED(Validator::Validate(error));
ICEBERG_RETURN_UNEXPECTED(error.Validate());
return error;
}

Expand All @@ -125,7 +124,7 @@ Result<ErrorResponse> ErrorResponseFromJson(const nlohmann::json& json) {
ErrorResponse response;
ICEBERG_ASSIGN_OR_RAISE(auto error_json, GetJsonValue<nlohmann::json>(json, kError));
ICEBERG_ASSIGN_OR_RAISE(response.error, ErrorModelFromJson(error_json));
ICEBERG_RETURN_UNEXPECTED(Validator::Validate(response));
ICEBERG_RETURN_UNEXPECTED(response.Validate());
return response;
}

Expand All @@ -144,7 +143,7 @@ Result<CreateNamespaceRequest> CreateNamespaceRequestFromJson(
ICEBERG_ASSIGN_OR_RAISE(
request.properties,
GetJsonValueOrDefault<decltype(request.properties)>(json, kProperties));
ICEBERG_RETURN_UNEXPECTED(Validator::Validate(request));
ICEBERG_RETURN_UNEXPECTED(request.Validate());
return request;
}

Expand All @@ -162,7 +161,7 @@ Result<UpdateNamespacePropertiesRequest> UpdateNamespacePropertiesRequestFromJso
request.removals, GetJsonValueOrDefault<std::vector<std::string>>(json, kRemovals));
ICEBERG_ASSIGN_OR_RAISE(
request.updates, GetJsonValueOrDefault<decltype(request.updates)>(json, kUpdates));
ICEBERG_RETURN_UNEXPECTED(Validator::Validate(request));
ICEBERG_RETURN_UNEXPECTED(request.Validate());
return request;
}

Expand All @@ -183,7 +182,7 @@ Result<RegisterTableRequest> RegisterTableRequestFromJson(const nlohmann::json&
GetJsonValue<std::string>(json, kMetadataLocation));
ICEBERG_ASSIGN_OR_RAISE(request.overwrite,
GetJsonValueOrDefault<bool>(json, kOverwrite, false));
ICEBERG_RETURN_UNEXPECTED(Validator::Validate(request));
ICEBERG_RETURN_UNEXPECTED(request.Validate());
return request;
}

Expand All @@ -201,7 +200,7 @@ Result<RenameTableRequest> RenameTableRequestFromJson(const nlohmann::json& json
ICEBERG_ASSIGN_OR_RAISE(auto dest_json,
GetJsonValue<nlohmann::json>(json, kDestination));
ICEBERG_ASSIGN_OR_RAISE(request.destination, TableIdentifierFromJson(dest_json));
ICEBERG_RETURN_UNEXPECTED(Validator::Validate(request));
ICEBERG_RETURN_UNEXPECTED(request.Validate());
return request;
}

Expand Down Expand Up @@ -248,7 +247,7 @@ Result<ListNamespacesResponse> ListNamespacesResponseFromJson(
ICEBERG_ASSIGN_OR_RAISE(auto ns, NamespaceFromJson(ns_json));
response.namespaces.push_back(std::move(ns));
}
ICEBERG_RETURN_UNEXPECTED(Validator::Validate(response));
ICEBERG_RETURN_UNEXPECTED(response.Validate());
return response;
}

Expand Down Expand Up @@ -304,7 +303,7 @@ Result<UpdateNamespacePropertiesResponse> UpdateNamespacePropertiesResponseFromJ
response.removed, GetJsonValueOrDefault<std::vector<std::string>>(json, kRemoved));
ICEBERG_ASSIGN_OR_RAISE(
response.missing, GetJsonValueOrDefault<std::vector<std::string>>(json, kMissing));
ICEBERG_RETURN_UNEXPECTED(Validator::Validate(response));
ICEBERG_RETURN_UNEXPECTED(response.Validate());
return response;
}

Expand All @@ -329,7 +328,7 @@ Result<ListTablesResponse> ListTablesResponseFromJson(const nlohmann::json& json
ICEBERG_ASSIGN_OR_RAISE(auto identifier, TableIdentifierFromJson(id_json));
response.identifiers.push_back(std::move(identifier));
}
ICEBERG_RETURN_UNEXPECTED(Validator::Validate(response));
ICEBERG_RETURN_UNEXPECTED(response.Validate());
return response;
}

Expand Down
11 changes: 2 additions & 9 deletions src/iceberg/catalog/rest/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,7 @@
# specific language governing permissions and limitations
# under the License.

iceberg_rest_sources = files(
'json_internal.cc',
'rest_catalog.cc',
'validator.cc',
)
iceberg_rest_sources = files('json_internal.cc', 'rest_catalog.cc')
# cpr does not export symbols, so on Windows it must
# be used as a static lib
cpr_needs_static = (
Expand Down Expand Up @@ -50,7 +46,4 @@ iceberg_rest_dep = declare_dependency(
meson.override_dependency('iceberg-rest', iceberg_rest_dep)
pkg.generate(iceberg_rest_lib)

install_headers(
['rest_catalog.h', 'types.h', 'json_internal.h', 'validator.h'],
subdir: 'iceberg/catalog/rest',
)
install_headers(['rest_catalog.h', 'types.h'], subdir: 'iceberg/catalog/rest')
113 changes: 113 additions & 0 deletions src/iceberg/catalog/rest/types.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,19 @@

#pragma once

#include <algorithm>
#include <format>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>

#include "iceberg/catalog/rest/iceberg_rest_export.h"
#include "iceberg/result.h"
#include "iceberg/table_identifier.h"
#include "iceberg/type_fwd.h"
#include "iceberg/util/formatter_internal.h"
#include "iceberg/util/macros.h"

/// \file iceberg/catalog/rest/types.h
/// Request and response types for Iceberg REST Catalog API.
Expand All @@ -38,6 +43,15 @@ struct ICEBERG_REST_EXPORT CatalogConfig {
std::unordered_map<std::string, std::string> defaults; // required
std::unordered_map<std::string, std::string> overrides; // required
std::vector<std::string> endpoints;

/// \brief Validates the CatalogConfig.
Status Validate() const {
// TODO(Li Feiyang): Add an invalidEndpoint test that validates endpoint format.
// See:
// https://github.com/apache/iceberg/blob/main/core/src/test/java/org/apache/iceberg/rest/responses/TestConfigResponseParser.java#L164
// for reference.
return {};
}
};

/// \brief JSON error payload returned in a response with further details on the error.
Expand All @@ -46,36 +60,112 @@ struct ICEBERG_REST_EXPORT ErrorModel {
std::string type; // required
uint32_t code; // required
std::vector<std::string> stack;

/// \brief Validates the ErrorModel.
Status Validate() const {
if (message.empty() || type.empty()) {
return Invalid("Invalid error model: missing required fields");
}

if (code < 400 || code > 600) {
return Invalid("Invalid error model: code {} is out of range [400, 600]", code);
}

// stack is optional, no validation needed
return {};
}
};

/// \brief Error response body returned in a response.
struct ICEBERG_REST_EXPORT ErrorResponse {
ErrorModel error; // required

/// \brief Validates the ErrorResponse.
// We don't validate the error field because ErrorModel::Validate has been called in the
// FromJson.
Status Validate() const { return {}; }
};

/// \brief Request to create a namespace.
struct ICEBERG_REST_EXPORT CreateNamespaceRequest {
Namespace namespace_; // required
std::unordered_map<std::string, std::string> properties;

/// \brief Validates the CreateNamespaceRequest.
Status Validate() const { return {}; }
};

/// \brief Update or delete namespace properties request.
struct ICEBERG_REST_EXPORT UpdateNamespacePropertiesRequest {
std::vector<std::string> removals;
std::unordered_map<std::string, std::string> updates;

/// \brief Validates the UpdateNamespacePropertiesRequest.
Status Validate() const {
// keys in updates and removals must not overlap
if (removals.empty() || updates.empty()) {
return {};
}

auto extract_and_sort = [](const auto& container, auto key_extractor) {
std::vector<std::string_view> result;
result.reserve(container.size());
for (const auto& item : container) {
result.push_back(std::string_view{key_extractor(item)});
}
std::ranges::sort(result);
return result;
};

auto sorted_removals =
extract_and_sort(removals, [](const auto& s) -> const auto& { return s; });
auto sorted_update_keys = extract_and_sort(
updates, [](const auto& pair) -> const auto& { return pair.first; });

std::vector<std::string_view> common;
std::ranges::set_intersection(sorted_removals, sorted_update_keys,
std::back_inserter(common));

if (!common.empty()) {
return Invalid(
"Invalid namespace update: cannot simultaneously set and remove keys: {}",
common);
}
return {};
}
};

/// \brief Request to register a table.
struct ICEBERG_REST_EXPORT RegisterTableRequest {
std::string name; // required
std::string metadata_location; // required
bool overwrite = false;

/// \brief Validates the RegisterTableRequest.
Status Validate() const {
if (name.empty()) {
return Invalid("Missing table name");
}

if (metadata_location.empty()) {
return Invalid("Empty metadata location");
}

return {};
}
};

/// \brief Request to rename a table.
struct ICEBERG_REST_EXPORT RenameTableRequest {
TableIdentifier source; // required
TableIdentifier destination; // required

/// \brief Validates the RenameTableRequest.
Status Validate() const {
ICEBERG_RETURN_UNEXPECTED(source.Validate());
ICEBERG_RETURN_UNEXPECTED(destination.Validate());
return {};
}
};

/// \brief An opaque token that allows clients to make use of pagination for list APIs.
Expand All @@ -87,6 +177,14 @@ struct ICEBERG_REST_EXPORT LoadTableResult {
std::shared_ptr<TableMetadata> metadata; // required
std::unordered_map<std::string, std::string> config;
// TODO(Li Feiyang): Add std::shared_ptr<StorageCredential> storage_credential;

/// \brief Validates the LoadTableResult.
Status Validate() const {
if (!metadata) {
return Invalid("Invalid metadata: null");
}
return {};
}
};

/// \brief Alias of LoadTableResult used as the body of CreateTableResponse
Expand All @@ -99,31 +197,46 @@ using LoadTableResponse = LoadTableResult;
struct ICEBERG_REST_EXPORT ListNamespacesResponse {
PageToken next_page_token;
std::vector<Namespace> namespaces;

/// \brief Validates the ListNamespacesResponse.
Status Validate() const { return {}; }
};

/// \brief Response body after creating a namespace.
struct ICEBERG_REST_EXPORT CreateNamespaceResponse {
Namespace namespace_; // required
std::unordered_map<std::string, std::string> properties;

/// \brief Validates the CreateNamespaceResponse.
Status Validate() const { return {}; }
};

/// \brief Response body for loading namespace properties.
struct ICEBERG_REST_EXPORT GetNamespaceResponse {
Namespace namespace_; // required
std::unordered_map<std::string, std::string> properties;

/// \brief Validates the GetNamespaceResponse.
Status Validate() const { return {}; }
};

/// \brief Response body after updating namespace properties.
struct ICEBERG_REST_EXPORT UpdateNamespacePropertiesResponse {
std::vector<std::string> updated; // required
std::vector<std::string> removed; // required
std::vector<std::string> missing;

/// \brief Validates the UpdateNamespacePropertiesResponse.
Status Validate() const { return {}; }
};

/// \brief Response body for listing tables in a namespace.
struct ICEBERG_REST_EXPORT ListTablesResponse {
PageToken next_page_token;
std::vector<TableIdentifier> identifiers;

/// \brief Validates the ListTablesResponse.
Status Validate() const { return {}; }
};

} // namespace iceberg::rest
Loading
Loading