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 f93f54da feat: implement REST catalog namespace operations (#404)
f93f54da is described below

commit f93f54da326581808451e377085d39bca11ca0e4
Author: Feiyang Li <[email protected]>
AuthorDate: Thu Dec 11 17:28:45 2025 +0800

    feat: implement REST catalog namespace operations (#404)
---
 src/iceberg/catalog/rest/http_client.cc     |  36 +++++--
 src/iceberg/catalog/rest/json_internal.cc   |   3 +
 src/iceberg/catalog/rest/rest_catalog.cc    |  74 +++++++++++---
 src/iceberg/catalog/rest/rest_util.cc       | 131 ++++++++++++++++++++++++
 src/iceberg/catalog/rest/rest_util.h        |   9 ++
 src/iceberg/catalog/rest/types.h            |   4 +-
 src/iceberg/test/rest_catalog_test.cc       | 151 ++++++++++++++++++++++++++++
 src/iceberg/test/rest_json_internal_test.cc |  20 ++--
 8 files changed, 394 insertions(+), 34 deletions(-)

diff --git a/src/iceberg/catalog/rest/http_client.cc 
b/src/iceberg/catalog/rest/http_client.cc
index 3e70b9d9..d1138b78 100644
--- a/src/iceberg/catalog/rest/http_client.cc
+++ b/src/iceberg/catalog/rest/http_client.cc
@@ -25,6 +25,7 @@
 #include "iceberg/catalog/rest/constant.h"
 #include "iceberg/catalog/rest/error_handlers.h"
 #include "iceberg/catalog/rest/json_internal.h"
+#include "iceberg/catalog/rest/rest_util.h"
 #include "iceberg/json_internal.h"
 #include "iceberg/result.h"
 #include "iceberg/util/macros.h"
@@ -63,6 +64,9 @@ std::unordered_map<std::string, std::string> 
HttpResponse::headers() const {
 
 namespace {
 
+/// \brief Default error type for unparseable REST responses.
+constexpr std::string_view kRestExceptionType = "RESTException";
+
 /// \brief Merges global default headers with request-specific headers.
 ///
 /// Combines the global headers derived from RestCatalogProperties with the 
headers
@@ -96,16 +100,36 @@ bool IsSuccessful(int32_t status_code) {
          || status_code == 304;  // Not Modified
 }
 
+/// \brief Builds a default ErrorResponse when the response body cannot be 
parsed.
+ErrorResponse BuildDefaultErrorResponse(const cpr::Response& response) {
+  return {
+      .code = static_cast<uint32_t>(response.status_code),
+      .type = std::string(kRestExceptionType),
+      .message = !response.reason.empty() ? response.reason
+                                          : 
GetStandardReasonPhrase(response.status_code),
+  };
+}
+
+/// \brief Tries to parse the response body as an ErrorResponse.
+Result<ErrorResponse> TryParseErrorResponse(const std::string& text) {
+  if (text.empty()) {
+    return InvalidArgument("Empty response body");
+  }
+  ICEBERG_ASSIGN_OR_RAISE(auto json_result, FromJsonString(text));
+  ICEBERG_ASSIGN_OR_RAISE(auto error_result, 
ErrorResponseFromJson(json_result));
+  return error_result;
+}
+
 /// \brief Handles failure responses by invoking the provided error handler.
 Status HandleFailureResponse(const cpr::Response& response,
                              const ErrorHandler& error_handler) {
-  if (!IsSuccessful(response.status_code)) {
-    // TODO(gangwu): response status code is lost, wrap it with RestError.
-    ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.text));
-    ICEBERG_ASSIGN_OR_RAISE(auto error_response, ErrorResponseFromJson(json));
-    return error_handler.Accept(error_response);
+  if (IsSuccessful(response.status_code)) {
+    return {};
   }
-  return {};
+  auto parse_result = TryParseErrorResponse(response.text);
+  const ErrorResponse final_error =
+      parse_result.value_or(BuildDefaultErrorResponse(response));
+  return error_handler.Accept(final_error);
 }
 
 }  // namespace
diff --git a/src/iceberg/catalog/rest/json_internal.cc 
b/src/iceberg/catalog/rest/json_internal.cc
index 61fce938..c60b406d 100644
--- a/src/iceberg/catalog/rest/json_internal.cc
+++ b/src/iceberg/catalog/rest/json_internal.cc
@@ -213,6 +213,7 @@ Result<LoadTableResult> LoadTableResultFromJson(const 
nlohmann::json& json) {
   ICEBERG_ASSIGN_OR_RAISE(result.metadata, 
TableMetadataFromJson(metadata_json));
   ICEBERG_ASSIGN_OR_RAISE(result.config,
                           GetJsonValueOrDefault<decltype(result.config)>(json, 
kConfig));
+  ICEBERG_RETURN_UNEXPECTED(result.Validate());
   return result;
 }
 
@@ -257,6 +258,7 @@ Result<CreateNamespaceResponse> 
CreateNamespaceResponseFromJson(
   ICEBERG_ASSIGN_OR_RAISE(
       response.properties,
       GetJsonValueOrDefault<decltype(response.properties)>(json, kProperties));
+  ICEBERG_RETURN_UNEXPECTED(response.Validate());
   return response;
 }
 
@@ -274,6 +276,7 @@ Result<GetNamespaceResponse> 
GetNamespaceResponseFromJson(const nlohmann::json&
   ICEBERG_ASSIGN_OR_RAISE(
       response.properties,
       GetJsonValueOrDefault<decltype(response.properties)>(json, kProperties));
+  ICEBERG_RETURN_UNEXPECTED(response.Validate());
   return response;
 }
 
diff --git a/src/iceberg/catalog/rest/rest_catalog.cc 
b/src/iceberg/catalog/rest/rest_catalog.cc
index e4553ace..4a77f658 100644
--- a/src/iceberg/catalog/rest/rest_catalog.cc
+++ b/src/iceberg/catalog/rest/rest_catalog.cc
@@ -34,7 +34,9 @@
 #include "iceberg/catalog/rest/rest_catalog.h"
 #include "iceberg/catalog/rest/rest_util.h"
 #include "iceberg/json_internal.h"
+#include "iceberg/partition_spec.h"
 #include "iceberg/result.h"
+#include "iceberg/schema.h"
 #include "iceberg/table.h"
 #include "iceberg/util/macros.h"
 
@@ -99,7 +101,7 @@ Result<std::vector<Namespace>> 
RestCatalog::ListNamespaces(const Namespace& ns)
     if (!next_token.empty()) {
       params[kQueryParamPageToken] = next_token;
     }
-    ICEBERG_ASSIGN_OR_RAISE(const auto& response,
+    ICEBERG_ASSIGN_OR_RAISE(const auto response,
                             client_->Get(endpoint, params, /*headers=*/{},
                                          *NamespaceErrorHandler::Instance()));
     ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body()));
@@ -115,29 +117,69 @@ Result<std::vector<Namespace>> 
RestCatalog::ListNamespaces(const Namespace& ns)
 }
 
 Status RestCatalog::CreateNamespace(
-    [[maybe_unused]] const Namespace& ns,
-    [[maybe_unused]] const std::unordered_map<std::string, std::string>& 
properties) {
-  return NotImplemented("Not implemented");
+    const Namespace& ns, const std::unordered_map<std::string, std::string>& 
properties) {
+  ICEBERG_ASSIGN_OR_RAISE(auto endpoint, paths_->Namespaces());
+  CreateNamespaceRequest request{.namespace_ = ns, .properties = properties};
+  ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request)));
+  ICEBERG_ASSIGN_OR_RAISE(const auto response,
+                          client_->Post(endpoint, json_request, /*headers=*/{},
+                                        *NamespaceErrorHandler::Instance()));
+  ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body()));
+  ICEBERG_ASSIGN_OR_RAISE(auto create_response, 
CreateNamespaceResponseFromJson(json));
+  return {};
 }
 
 Result<std::unordered_map<std::string, std::string>> 
RestCatalog::GetNamespaceProperties(
-    [[maybe_unused]] const Namespace& ns) const {
-  return NotImplemented("Not implemented");
-}
-
-Status RestCatalog::DropNamespace([[maybe_unused]] const Namespace& ns) {
-  return NotImplemented("Not implemented");
+    const Namespace& ns) const {
+  ICEBERG_ASSIGN_OR_RAISE(auto endpoint, paths_->Namespace_(ns));
+  ICEBERG_ASSIGN_OR_RAISE(const auto response,
+                          client_->Get(endpoint, /*params=*/{}, /*headers=*/{},
+                                       *NamespaceErrorHandler::Instance()));
+  ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body()));
+  ICEBERG_ASSIGN_OR_RAISE(auto get_response, 
GetNamespaceResponseFromJson(json));
+  return get_response.properties;
 }
 
-Result<bool> RestCatalog::NamespaceExists([[maybe_unused]] const Namespace& 
ns) const {
-  return NotImplemented("Not implemented");
+Status RestCatalog::DropNamespace(const Namespace& ns) {
+  ICEBERG_ASSIGN_OR_RAISE(auto endpoint, paths_->Namespace_(ns));
+  ICEBERG_ASSIGN_OR_RAISE(
+      const auto response,
+      client_->Delete(endpoint, /*headers=*/{}, 
*DropNamespaceErrorHandler::Instance()));
+  return {};
+}
+
+Result<bool> RestCatalog::NamespaceExists(const Namespace& ns) const {
+  ICEBERG_ASSIGN_OR_RAISE(auto endpoint, paths_->Namespace_(ns));
+  // TODO(Feiyang Li): checks if the server supports the namespace exists 
endpoint, if
+  // not, triggers a fallback mechanism
+  auto response_or_error =
+      client_->Head(endpoint, /*headers=*/{}, 
*NamespaceErrorHandler::Instance());
+  if (!response_or_error.has_value()) {
+    const auto& error = response_or_error.error();
+    // catch NoSuchNamespaceException/404 and return false
+    if (error.kind == ErrorKind::kNoSuchNamespace) {
+      return false;
+    }
+    ICEBERG_RETURN_UNEXPECTED(response_or_error);
+  }
+  return true;
 }
 
 Status RestCatalog::UpdateNamespaceProperties(
-    [[maybe_unused]] const Namespace& ns,
-    [[maybe_unused]] const std::unordered_map<std::string, std::string>& 
updates,
-    [[maybe_unused]] const std::unordered_set<std::string>& removals) {
-  return NotImplemented("Not implemented");
+    const Namespace& ns, const std::unordered_map<std::string, std::string>& 
updates,
+    const std::unordered_set<std::string>& removals) {
+  ICEBERG_ASSIGN_OR_RAISE(auto endpoint, paths_->NamespaceProperties(ns));
+  UpdateNamespacePropertiesRequest request{
+      .removals = std::vector<std::string>(removals.begin(), removals.end()),
+      .updates = updates};
+  ICEBERG_ASSIGN_OR_RAISE(auto json_request, ToJsonString(ToJson(request)));
+  ICEBERG_ASSIGN_OR_RAISE(const auto response,
+                          client_->Post(endpoint, json_request, /*headers=*/{},
+                                        *NamespaceErrorHandler::Instance()));
+  ICEBERG_ASSIGN_OR_RAISE(auto json, FromJsonString(response.body()));
+  ICEBERG_ASSIGN_OR_RAISE(auto update_response,
+                          UpdateNamespacePropertiesResponseFromJson(json));
+  return {};
 }
 
 Result<std::vector<TableIdentifier>> RestCatalog::ListTables(
diff --git a/src/iceberg/catalog/rest/rest_util.cc 
b/src/iceberg/catalog/rest/rest_util.cc
index 5a0f166d..a1a63fa1 100644
--- a/src/iceberg/catalog/rest/rest_util.cc
+++ b/src/iceberg/catalog/rest/rest_util.cc
@@ -19,6 +19,8 @@
 
 #include "iceberg/catalog/rest/rest_util.h"
 
+#include <format>
+
 #include <cpr/util.h>
 
 #include "iceberg/table_identifier.h"
@@ -120,4 +122,133 @@ std::unordered_map<std::string, std::string> MergeConfigs(
   return merged;
 }
 
+std::string GetStandardReasonPhrase(int32_t status_code) {
+  switch (status_code) {
+    case 100:
+      return "Continue";
+    case 101:
+      return "Switching Protocols";
+    case 102:
+      return "Processing";
+    case 103:
+      return "Early Hints";
+    case 200:
+      return "OK";
+    case 201:
+      return "Created";
+    case 202:
+      return "Accepted";
+    case 203:
+      return "Non Authoritative Information";
+    case 204:
+      return "No Content";
+    case 205:
+      return "Reset Content";
+    case 206:
+      return "Partial Content";
+    case 207:
+      return "Multi-Status";
+    case 208:
+      return "Already Reported";
+    case 226:
+      return "IM Used";
+    case 300:
+      return "Multiple Choices";
+    case 301:
+      return "Moved Permanently";
+    case 302:
+      return "Moved Temporarily";
+    case 303:
+      return "See Other";
+    case 304:
+      return "Not Modified";
+    case 305:
+      return "Use Proxy";
+    case 307:
+      return "Temporary Redirect";
+    case 308:
+      return "Permanent Redirect";
+    case 400:
+      return "Bad Request";
+    case 401:
+      return "Unauthorized";
+    case 402:
+      return "Payment Required";
+    case 403:
+      return "Forbidden";
+    case 404:
+      return "Not Found";
+    case 405:
+      return "Method Not Allowed";
+    case 406:
+      return "Not Acceptable";
+    case 407:
+      return "Proxy Authentication Required";
+    case 408:
+      return "Request Timeout";
+    case 409:
+      return "Conflict";
+    case 410:
+      return "Gone";
+    case 411:
+      return "Length Required";
+    case 412:
+      return "Precondition Failed";
+    case 413:
+      return "Request Too Long";
+    case 414:
+      return "Request-URI Too Long";
+    case 415:
+      return "Unsupported Media Type";
+    case 416:
+      return "Requested Range Not Satisfiable";
+    case 417:
+      return "Expectation Failed";
+    case 421:
+      return "Misdirected Request";
+    case 422:
+      return "Unprocessable Content";
+    case 423:
+      return "Locked";
+    case 424:
+      return "Failed Dependency";
+    case 425:
+      return "Too Early";
+    case 426:
+      return "Upgrade Required";
+    case 428:
+      return "Precondition Required";
+    case 429:
+      return "Too Many Requests";
+    case 431:
+      return "Request Header Fields Too Large";
+    case 451:
+      return "Unavailable For Legal Reasons";
+    case 500:
+      return "Internal Server Error";
+    case 501:
+      return "Not Implemented";
+    case 502:
+      return "Bad Gateway";
+    case 503:
+      return "Service Unavailable";
+    case 504:
+      return "Gateway Timeout";
+    case 505:
+      return "Http Version Not Supported";
+    case 506:
+      return "Variant Also Negotiates";
+    case 507:
+      return "Insufficient Storage";
+    case 508:
+      return "Loop Detected";
+    case 510:
+      return "Not Extended";
+    case 511:
+      return "Network Authentication Required";
+    default:
+      return std::format("HTTP {}", status_code);
+  }
+}
+
 }  // namespace iceberg::rest
diff --git a/src/iceberg/catalog/rest/rest_util.h 
b/src/iceberg/catalog/rest/rest_util.h
index 895bb2fb..fde67a84 100644
--- a/src/iceberg/catalog/rest/rest_util.h
+++ b/src/iceberg/catalog/rest/rest_util.h
@@ -81,4 +81,13 @@ ICEBERG_REST_EXPORT std::unordered_map<std::string, 
std::string> MergeConfigs(
     const std::unordered_map<std::string, std::string>& client_configs,
     const std::unordered_map<std::string, std::string>& server_overrides);
 
+/// \brief Get the standard HTTP reason phrase for a status code.
+///
+/// \details Returns the standard English reason phrase for common HTTP status 
codes.
+/// For unknown status codes, returns a generic "HTTP {code}" message.
+/// \param status_code The HTTP status code (e.g., 200, 404, 500).
+/// \return The standard reason phrase string (e.g., "OK", "Not Found", 
"Internal Server
+/// Error").
+ICEBERG_REST_EXPORT std::string GetStandardReasonPhrase(int32_t status_code);
+
 }  // namespace iceberg::rest
diff --git a/src/iceberg/catalog/rest/types.h b/src/iceberg/catalog/rest/types.h
index dbc772ec..7760e178 100644
--- a/src/iceberg/catalog/rest/types.h
+++ b/src/iceberg/catalog/rest/types.h
@@ -53,9 +53,9 @@ struct ICEBERG_REST_EXPORT CatalogConfig {
 
 /// \brief JSON error payload returned in a response with further details on 
the error.
 struct ICEBERG_REST_EXPORT ErrorResponse {
-  std::string message;  // required
-  std::string type;     // required
   uint32_t code;        // required
+  std::string type;     // required
+  std::string message;  // required
   std::vector<std::string> stack;
 
   /// \brief Validates the ErrorResponse.
diff --git a/src/iceberg/test/rest_catalog_test.cc 
b/src/iceberg/test/rest_catalog_test.cc
index f91782a0..49c527f6 100644
--- a/src/iceberg/test/rest_catalog_test.cc
+++ b/src/iceberg/test/rest_catalog_test.cc
@@ -148,4 +148,155 @@ TEST_F(RestCatalogIntegrationTest, ListNamespaces) {
   EXPECT_TRUE(result->empty());
 }
 
+TEST_F(RestCatalogIntegrationTest, CreateNamespace) {
+  auto catalog_result = CreateCatalog();
+  ASSERT_THAT(catalog_result, IsOk());
+  auto& catalog = catalog_result.value();
+
+  // Create a simple namespace
+  Namespace ns{.levels = {"test_ns"}};
+  auto status = catalog->CreateNamespace(ns, {});
+  EXPECT_THAT(status, IsOk());
+
+  // Verify it was created by listing
+  Namespace root{.levels = {}};
+  auto list_result = catalog->ListNamespaces(root);
+  ASSERT_THAT(list_result, IsOk());
+  EXPECT_EQ(list_result->size(), 1);
+  EXPECT_EQ(list_result->at(0).levels, std::vector<std::string>{"test_ns"});
+}
+
+TEST_F(RestCatalogIntegrationTest, CreateNamespaceWithProperties) {
+  auto catalog_result = CreateCatalog();
+  ASSERT_THAT(catalog_result, IsOk());
+  auto& catalog = catalog_result.value();
+
+  // Create namespace with properties
+  Namespace ns{.levels = {"test_ns_props"}};
+  std::unordered_map<std::string, std::string> properties{
+      {"owner", "test_user"}, {"description", "Test namespace with 
properties"}};
+  auto status = catalog->CreateNamespace(ns, properties);
+  EXPECT_THAT(status, IsOk());
+
+  // Verify properties were set
+  auto props_result = catalog->GetNamespaceProperties(ns);
+  ASSERT_THAT(props_result, IsOk());
+  EXPECT_EQ(props_result->at("owner"), "test_user");
+  EXPECT_EQ(props_result->at("description"), "Test namespace with properties");
+}
+
+TEST_F(RestCatalogIntegrationTest, CreateNestedNamespace) {
+  auto catalog_result = CreateCatalog();
+  ASSERT_THAT(catalog_result, IsOk());
+  auto& catalog = catalog_result.value();
+
+  // Create parent namespace
+  Namespace parent{.levels = {"parent"}};
+  auto status = catalog->CreateNamespace(parent, {});
+  EXPECT_THAT(status, IsOk());
+
+  // Create nested namespace
+  Namespace child{.levels = {"parent", "child"}};
+  status = catalog->CreateNamespace(child, {});
+  EXPECT_THAT(status, IsOk());
+
+  // Verify nested namespace exists
+  auto list_result = catalog->ListNamespaces(parent);
+  ASSERT_THAT(list_result, IsOk());
+  EXPECT_EQ(list_result->size(), 1);
+  EXPECT_EQ(list_result->at(0).levels, (std::vector<std::string>{"parent", 
"child"}));
+}
+
+TEST_F(RestCatalogIntegrationTest, GetNamespaceProperties) {
+  auto catalog_result = CreateCatalog();
+  ASSERT_THAT(catalog_result, IsOk());
+  auto& catalog = catalog_result.value();
+
+  // Create namespace with properties
+  Namespace ns{.levels = {"test_get_props"}};
+  std::unordered_map<std::string, std::string> properties{{"key1", "value1"},
+                                                          {"key2", "value2"}};
+  auto status = catalog->CreateNamespace(ns, properties);
+  EXPECT_THAT(status, IsOk());
+
+  // Get properties
+  auto props_result = catalog->GetNamespaceProperties(ns);
+  ASSERT_THAT(props_result, IsOk());
+  EXPECT_EQ(props_result->at("key1"), "value1");
+  EXPECT_EQ(props_result->at("key2"), "value2");
+}
+
+TEST_F(RestCatalogIntegrationTest, NamespaceExists) {
+  auto catalog_result = CreateCatalog();
+  ASSERT_THAT(catalog_result, IsOk());
+  auto& catalog = catalog_result.value();
+
+  // Check non-existent namespace
+  Namespace ns{.levels = {"non_existent"}};
+  auto exists_result = catalog->NamespaceExists(ns);
+  ASSERT_THAT(exists_result, IsOk());
+  EXPECT_FALSE(*exists_result);
+
+  // Create namespace
+  auto status = catalog->CreateNamespace(ns, {});
+  EXPECT_THAT(status, IsOk());
+
+  // Check it now exists
+  exists_result = catalog->NamespaceExists(ns);
+  ASSERT_THAT(exists_result, IsOk());
+  EXPECT_TRUE(*exists_result);
+}
+
+TEST_F(RestCatalogIntegrationTest, UpdateNamespaceProperties) {
+  auto catalog_result = CreateCatalog();
+  ASSERT_THAT(catalog_result, IsOk());
+  auto& catalog = catalog_result.value();
+
+  // Create namespace with initial properties
+  Namespace ns{.levels = {"test_update"}};
+  std::unordered_map<std::string, std::string> initial_props{{"key1", 
"value1"},
+                                                             {"key2", 
"value2"}};
+  auto status = catalog->CreateNamespace(ns, initial_props);
+  EXPECT_THAT(status, IsOk());
+
+  // Update properties: modify key1, add key3, remove key2
+  std::unordered_map<std::string, std::string> updates{{"key1", 
"updated_value1"},
+                                                       {"key3", "value3"}};
+  std::unordered_set<std::string> removals{"key2"};
+  status = catalog->UpdateNamespaceProperties(ns, updates, removals);
+  EXPECT_THAT(status, IsOk());
+
+  // Verify updated properties
+  auto props_result = catalog->GetNamespaceProperties(ns);
+  ASSERT_THAT(props_result, IsOk());
+  EXPECT_EQ(props_result->at("key1"), "updated_value1");
+  EXPECT_EQ(props_result->at("key3"), "value3");
+  EXPECT_EQ(props_result->count("key2"), 0);  // Should be removed
+}
+
+TEST_F(RestCatalogIntegrationTest, DropNamespace) {
+  auto catalog_result = CreateCatalog();
+  ASSERT_THAT(catalog_result, IsOk());
+  auto& catalog = catalog_result.value();
+
+  // Create namespace
+  Namespace ns{.levels = {"test_drop"}};
+  auto status = catalog->CreateNamespace(ns, {});
+  EXPECT_THAT(status, IsOk());
+
+  // Verify it exists
+  auto exists_result = catalog->NamespaceExists(ns);
+  ASSERT_THAT(exists_result, IsOk());
+  EXPECT_TRUE(*exists_result);
+
+  // Drop namespace
+  status = catalog->DropNamespace(ns);
+  EXPECT_THAT(status, IsOk());
+
+  // Verify it no longer exists
+  exists_result = catalog->NamespaceExists(ns);
+  ASSERT_THAT(exists_result, IsOk());
+  EXPECT_FALSE(*exists_result);
+}
+
 }  // namespace iceberg::rest
diff --git a/src/iceberg/test/rest_json_internal_test.cc 
b/src/iceberg/test/rest_json_internal_test.cc
index 2cae201f..ca2671fa 100644
--- a/src/iceberg/test/rest_json_internal_test.cc
+++ b/src/iceberg/test/rest_json_internal_test.cc
@@ -870,26 +870,26 @@ INSTANTIATE_TEST_SUITE_P(
             .test_name = "WithoutStack",
             .expected_json_str =
                 R"({"error":{"message":"The given namespace does not 
exist","type":"NoSuchNamespaceException","code":404}})",
-            .model = {.message = "The given namespace does not exist",
+            .model = {.code = 404,
                       .type = "NoSuchNamespaceException",
-                      .code = 404}},
+                      .message = "The given namespace does not exist"}},
         // Error with stack trace
         ErrorResponseParam{
             .test_name = "WithStack",
             .expected_json_str =
                 R"({"error":{"message":"The given namespace does not 
exist","type":"NoSuchNamespaceException","code":404,"stack":["a","b"]}})",
-            .model = {.message = "The given namespace does not exist",
+            .model = {.code = 404,
                       .type = "NoSuchNamespaceException",
-                      .code = 404,
+                      .message = "The given namespace does not exist",
                       .stack = {"a", "b"}}},
         // Different error type
         ErrorResponseParam{
             .test_name = "DifferentError",
             .expected_json_str =
                 R"({"error":{"message":"Internal server 
error","type":"InternalServerError","code":500,"stack":["line1","line2","line3"]}})",
-            .model = {.message = "Internal server error",
+            .model = {.code = 500,
                       .type = "InternalServerError",
-                      .code = 500,
+                      .message = "Internal server error",
                       .stack = {"line1", "line2", "line3"}}}),
     [](const ::testing::TestParamInfo<ErrorResponseParam>& info) {
       return info.param.test_name;
@@ -905,17 +905,17 @@ INSTANTIATE_TEST_SUITE_P(
             .test_name = "NullStack",
             .json_str =
                 R"({"error":{"message":"The given namespace does not 
exist","type":"NoSuchNamespaceException","code":404,"stack":null}})",
-            .expected_model = {.message = "The given namespace does not exist",
+            .expected_model = {.code = 404,
                                .type = "NoSuchNamespaceException",
-                               .code = 404}},
+                               .message = "The given namespace does not 
exist"}},
         // Stack field is missing (should deserialize to empty vector)
         ErrorResponseDeserializeParam{
             .test_name = "MissingStack",
             .json_str =
                 R"({"error":{"message":"The given namespace does not 
exist","type":"NoSuchNamespaceException","code":404}})",
-            .expected_model = {.message = "The given namespace does not exist",
+            .expected_model = {.code = 404,
                                .type = "NoSuchNamespaceException",
-                               .code = 404}}),
+                               .message = "The given namespace does not 
exist"}}),
     [](const ::testing::TestParamInfo<ErrorResponseDeserializeParam>& info) {
       return info.param.test_name;
     });

Reply via email to