This is an automated email from the ASF dual-hosted git repository.

gangwu pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-cpp.git


The following commit(s) were added to refs/heads/main by this push:
     new 7f7f85b  refactor: remove Validator and add Validate methods directly 
to rest models (#304)
7f7f85b is described below

commit 7f7f85b7b7b666a398bfc4ebd47f385578ce9c4e
Author: Li Feiyang <[email protected]>
AuthorDate: Wed Nov 12 10:21:52 2025 +0800

    refactor: remove Validator and add Validate methods directly to rest models 
(#304)
---
 src/iceberg/catalog/rest/CMakeLists.txt   |   2 +-
 src/iceberg/catalog/rest/json_internal.cc |  21 +++--
 src/iceberg/catalog/rest/meson.build      |  11 +--
 src/iceberg/catalog/rest/types.h          |  88 ++++++++++++++++++
 src/iceberg/catalog/rest/validator.cc     | 142 ------------------------------
 src/iceberg/catalog/rest/validator.h      |  87 ------------------
 src/iceberg/table_identifier.h            |   9 ++
 7 files changed, 110 insertions(+), 250 deletions(-)

diff --git a/src/iceberg/catalog/rest/CMakeLists.txt 
b/src/iceberg/catalog/rest/CMakeLists.txt
index 0244051..38d8972 100644
--- a/src/iceberg/catalog/rest/CMakeLists.txt
+++ b/src/iceberg/catalog/rest/CMakeLists.txt
@@ -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)
diff --git a/src/iceberg/catalog/rest/json_internal.cc 
b/src/iceberg/catalog/rest/json_internal.cc
index 404fe21..bdec822 100644
--- a/src/iceberg/catalog/rest/json_internal.cc
+++ b/src/iceberg/catalog/rest/json_internal.cc
@@ -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"
@@ -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;
 }
 
@@ -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;
 }
 
@@ -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;
 }
 
@@ -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;
 }
 
@@ -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;
 }
 
@@ -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;
 }
 
@@ -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;
 }
 
@@ -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;
 }
 
@@ -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;
 }
 
@@ -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;
 }
 
diff --git a/src/iceberg/catalog/rest/meson.build 
b/src/iceberg/catalog/rest/meson.build
index e8edc35..5f1f635 100644
--- a/src/iceberg/catalog/rest/meson.build
+++ b/src/iceberg/catalog/rest/meson.build
@@ -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 = (
@@ -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')
diff --git a/src/iceberg/catalog/rest/types.h b/src/iceberg/catalog/rest/types.h
index 15e9831..2e32f96 100644
--- a/src/iceberg/catalog/rest/types.h
+++ b/src/iceberg/catalog/rest/types.h
@@ -19,14 +19,18 @@
 
 #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/macros.h"
 
 /// \file iceberg/catalog/rest/types.h
 /// Request and response types for Iceberg REST Catalog API.
@@ -38,6 +42,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.
@@ -46,23 +59,55 @@ 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 {
+    for (const auto& key : removals) {
+      if (updates.contains(key)) {
+        return Invalid("Duplicate key to update and remove: {}", key);
+      }
+    }
+    return {};
+  }
 };
 
 /// \brief Request to register a table.
@@ -70,12 +115,32 @@ 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.
@@ -87,6 +152,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
@@ -99,18 +172,27 @@ 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.
@@ -118,12 +200,18 @@ 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
diff --git a/src/iceberg/catalog/rest/validator.cc 
b/src/iceberg/catalog/rest/validator.cc
deleted file mode 100644
index de25fa3..0000000
--- a/src/iceberg/catalog/rest/validator.cc
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * 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 "iceberg/catalog/rest/validator.h"
-
-#include <algorithm>
-#include <format>
-
-#include "iceberg/catalog/rest/types.h"
-#include "iceberg/result.h"
-#include "iceberg/util/formatter_internal.h"
-#include "iceberg/util/macros.h"
-
-namespace iceberg::rest {
-
-// Configuration and Error types
-
-Status Validator::Validate(const CatalogConfig& config) {
-  // 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 {};
-}
-
-Status Validator::Validate(const ErrorModel& error) {
-  if (error.message.empty() || error.type.empty()) {
-    return Invalid("Invalid error model: missing required fields");
-  }
-
-  if (error.code < 400 || error.code > 600) {
-    return Invalid("Invalid error model: code {} is out of range [400, 600]", 
error.code);
-  }
-
-  // stack is optional, no validation needed
-  return {};
-}
-
-// We don't validate the error field because ErrorModel::Validate has been 
called in the
-// FromJson.
-Status Validator::Validate(const ErrorResponse& response) { return {}; }
-
-// Namespace operations
-
-Status Validator::Validate(const ListNamespacesResponse& response) { return 
{}; }
-
-Status Validator::Validate(const CreateNamespaceRequest& request) { return {}; 
}
-
-Status Validator::Validate(const CreateNamespaceResponse& response) { return 
{}; }
-
-Status Validator::Validate(const GetNamespaceResponse& response) { return {}; }
-
-Status Validator::Validate(const UpdateNamespacePropertiesRequest& request) {
-  // keys in updates and removals must not overlap
-  if (request.removals.empty() || request.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(request.removals, [](const auto& s) -> const auto& { 
return s; });
-  auto sorted_update_keys = extract_and_sort(
-      request.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 {};
-}
-
-Status Validator::Validate(const UpdateNamespacePropertiesResponse& response) {
-  return {};
-}
-
-// Table operations
-
-Status Validator::Validate(const ListTablesResponse& response) { return {}; }
-
-Status Validator::Validate(const LoadTableResult& result) {
-  if (!result.metadata) {
-    return Invalid("Invalid metadata: null");
-  }
-  return {};
-}
-
-Status Validator::Validate(const RegisterTableRequest& request) {
-  if (request.name.empty()) {
-    return Invalid("Missing table name");
-  }
-
-  if (request.metadata_location.empty()) {
-    return Invalid("Empty metadata location");
-  }
-
-  return {};
-}
-
-Status Validator::Validate(const RenameTableRequest& request) {
-  ICEBERG_RETURN_UNEXPECTED(Validate(request.source));
-  ICEBERG_RETURN_UNEXPECTED(Validate(request.destination));
-  return {};
-}
-
-Status Validator::Validate(const TableIdentifier& identifier) {
-  if (identifier.name.empty()) {
-    return Invalid("Invalid table identifier: missing table name");
-  }
-  return {};
-}
-
-}  // namespace iceberg::rest
diff --git a/src/iceberg/catalog/rest/validator.h 
b/src/iceberg/catalog/rest/validator.h
deleted file mode 100644
index 2c80cab..0000000
--- a/src/iceberg/catalog/rest/validator.h
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * 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.
- */
-
-#pragma once
-
-#include "iceberg/catalog/rest/iceberg_rest_export.h"
-#include "iceberg/catalog/rest/types.h"
-#include "iceberg/result.h"
-
-/// \file iceberg/catalog/rest/validator.h
-/// Validator for REST Catalog API types.
-
-namespace iceberg::rest {
-
-/// \brief Validator for REST Catalog API types. Validation should be called 
after
-/// deserializing objects from external sources to ensure data integrity 
before the
-/// objects are used.
-class ICEBERG_REST_EXPORT Validator {
- public:
-  // Configuration and Error types
-
-  /// \brief Validates a CatalogConfig object.
-  static Status Validate(const CatalogConfig& config);
-
-  /// \brief Validates an ErrorModel object.
-  static Status Validate(const ErrorModel& error);
-
-  /// \brief Validates an ErrorResponse object.
-  static Status Validate(const ErrorResponse& response);
-
-  // Namespace operations
-
-  /// \brief Validates a ListNamespacesResponse object.
-  static Status Validate(const ListNamespacesResponse& response);
-
-  /// \brief Validates a CreateNamespaceRequest object.
-  static Status Validate(const CreateNamespaceRequest& request);
-
-  /// \brief Validates a CreateNamespaceResponse object.
-  static Status Validate(const CreateNamespaceResponse& response);
-
-  /// \brief Validates a GetNamespaceResponse object.
-  static Status Validate(const GetNamespaceResponse& response);
-
-  /// \brief Validates an UpdateNamespacePropertiesRequest object.
-  static Status Validate(const UpdateNamespacePropertiesRequest& request);
-
-  /// \brief Validates an UpdateNamespacePropertiesResponse object.
-  static Status Validate(const UpdateNamespacePropertiesResponse& response);
-
-  // Table operations
-
-  /// \brief Validates a ListTablesResponse object.
-  static Status Validate(const ListTablesResponse& response);
-
-  /// \brief Validates a LoadTableResult object.
-  static Status Validate(const LoadTableResult& result);
-
-  /// \brief Validates a RegisterTableRequest object.
-  static Status Validate(const RegisterTableRequest& request);
-
-  /// \brief Validates a RenameTableRequest object.
-  static Status Validate(const RenameTableRequest& request);
-
-  // Other types
-
-  /// \brief Validates a TableIdentifier object.
-  static Status Validate(const TableIdentifier& identifier);
-};
-
-}  // namespace iceberg::rest
diff --git a/src/iceberg/table_identifier.h b/src/iceberg/table_identifier.h
index 9aa5770..b145e75 100644
--- a/src/iceberg/table_identifier.h
+++ b/src/iceberg/table_identifier.h
@@ -26,6 +26,7 @@
 #include <vector>
 
 #include "iceberg/iceberg_export.h"
+#include "iceberg/result.h"
 
 namespace iceberg {
 
@@ -38,6 +39,14 @@ struct ICEBERG_EXPORT Namespace {
 struct ICEBERG_EXPORT TableIdentifier {
   Namespace ns;
   std::string name;
+
+  /// \brief Validates the TableIdentifier.
+  Status Validate() const {
+    if (name.empty()) {
+      return Invalid("Invalid table identifier: missing table name");
+    }
+    return {};
+  }
 };
 
 }  // namespace iceberg

Reply via email to