Bladeren bron

feat(errors): ApiError + status mapping + JSON request/response helpers

Fszontagh 2 maanden geleden
bovenliggende
commit
328a5872aa
7 gewijzigde bestanden met toevoegingen van 107 en 0 verwijderingen
  1. 2 0
      src/CMakeLists.txt
  2. 21 0
      src/errors.cpp
  3. 20 0
      src/errors.hpp
  4. 21 0
      src/json_http.cpp
  5. 13 0
      src/json_http.hpp
  6. 4 0
      tests/CMakeLists.txt
  7. 26 0
      tests/test_errors.cpp

+ 2 - 0
src/CMakeLists.txt

@@ -1,5 +1,7 @@
 add_library(vectorapi_core STATIC
     config.cpp
+    errors.cpp
+    json_http.cpp
 )
 target_include_directories(vectorapi_core PUBLIC
     ${CMAKE_CURRENT_SOURCE_DIR}

+ 21 - 0
src/errors.cpp

@@ -0,0 +1,21 @@
+#include "errors.hpp"
+
+namespace svapi {
+
+int httpStatus(ErrCode code) {
+    switch (code) {
+        case ErrCode::BadRequest:    return 400;
+        case ErrCode::Unauthorized:  return 401;
+        case ErrCode::NotFound:      return 404;
+        case ErrCode::Unprocessable: return 422;
+        case ErrCode::Unavailable:   return 503;
+        case ErrCode::Internal:      return 500;
+    }
+    return 500;
+}
+
+nlohmann::json errorBody(const std::string& code, const std::string& message) {
+    return nlohmann::json{{"error", {{"code", code}, {"message", message}}}};
+}
+
+} // namespace svapi

+ 20 - 0
src/errors.hpp

@@ -0,0 +1,20 @@
+#pragma once
+#include <stdexcept>
+#include <string>
+#include <nlohmann/json.hpp>
+
+namespace svapi {
+
+enum class ErrCode { BadRequest, Unauthorized, NotFound, Unprocessable, Unavailable, Internal };
+
+int httpStatus(ErrCode code);
+nlohmann::json errorBody(const std::string& code, const std::string& message);
+
+struct ApiError : std::runtime_error {
+    ErrCode code;
+    std::string slug;
+    ApiError(ErrCode c, std::string slug_, const std::string& msg)
+        : std::runtime_error(msg), code(c), slug(std::move(slug_)) {}
+};
+
+} // namespace svapi

+ 21 - 0
src/json_http.cpp

@@ -0,0 +1,21 @@
+#include "json_http.hpp"
+#include "errors.hpp"
+
+namespace svapi {
+
+nlohmann::json bodyJson(const httplib::Request& req) {
+    if (req.body.empty()) return nlohmann::json::object();
+    try {
+        return nlohmann::json::parse(req.body);
+    } catch (const std::exception& e) {
+        throw ApiError(ErrCode::BadRequest, "invalid_json",
+                       std::string("request body is not valid JSON: ") + e.what());
+    }
+}
+
+void sendJson(httplib::Response& res, int status, const nlohmann::json& body) {
+    res.status = status;
+    res.set_content(body.dump(), "application/json");
+}
+
+} // namespace svapi

+ 13 - 0
src/json_http.hpp

@@ -0,0 +1,13 @@
+#pragma once
+#include <httplib.h>
+#include <nlohmann/json.hpp>
+
+namespace svapi {
+
+// Parse a request body as JSON; throws ApiError(BadRequest) on failure.
+nlohmann::json bodyJson(const httplib::Request& req);
+
+// Serialize and send a JSON response with the given status.
+void sendJson(httplib::Response& res, int status, const nlohmann::json& body);
+
+} // namespace svapi

+ 4 - 0
tests/CMakeLists.txt

@@ -8,3 +8,7 @@ gtest_discover_tests(test_smoke)
 add_executable(test_config test_config.cpp)
 target_link_libraries(test_config PRIVATE vectorapi_core GTest::gtest_main)
 gtest_discover_tests(test_config)
+
+add_executable(test_errors test_errors.cpp)
+target_link_libraries(test_errors PRIVATE vectorapi_core GTest::gtest_main)
+gtest_discover_tests(test_errors)

+ 26 - 0
tests/test_errors.cpp

@@ -0,0 +1,26 @@
+#include <gtest/gtest.h>
+#include "errors.hpp"
+
+using namespace svapi;
+
+TEST(Errors, StatusMapping) {
+    EXPECT_EQ(httpStatus(ErrCode::BadRequest), 400);
+    EXPECT_EQ(httpStatus(ErrCode::Unauthorized), 401);
+    EXPECT_EQ(httpStatus(ErrCode::NotFound), 404);
+    EXPECT_EQ(httpStatus(ErrCode::Unprocessable), 422);
+    EXPECT_EQ(httpStatus(ErrCode::Unavailable), 503);
+    EXPECT_EQ(httpStatus(ErrCode::Internal), 500);
+}
+
+TEST(Errors, BodyShape) {
+    auto j = errorBody("not_found", "no such collection");
+    EXPECT_EQ(j["error"]["code"], "not_found");
+    EXPECT_EQ(j["error"]["message"], "no such collection");
+}
+
+TEST(Errors, ApiErrorCarriesFields) {
+    ApiError e(ErrCode::NotFound, "not_found", "gone");
+    EXPECT_EQ(e.code, ErrCode::NotFound);
+    EXPECT_EQ(e.slug, "not_found");
+    EXPECT_STREQ(e.what(), "gone");
+}