Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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 README.md
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ make coverage # Code coverage report (requires lcov)
include(FetchContent)
FetchContent_Declare(ncei-cpp
GIT_REPOSITORY https://github.com/Reddimus/ncei-cpp.git
GIT_TAG v0.3.0
GIT_TAG v0.3.1
)
FetchContent_MakeAvailable(ncei-cpp)
target_link_libraries(myapp PRIVATE ncei)
Expand Down
8 changes: 7 additions & 1 deletion src/core/rate_limit.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
#include "ncei/rate_limit.hpp"

#include <limits>

namespace ncei {

RateLimiter::RateLimiter(Config config)
Expand Down Expand Up @@ -81,7 +83,11 @@ std::uint16_t RateLimiter::available_tokens() const noexcept {
std::int32_t RateLimiter::daily_requests_remaining() const noexcept {
std::lock_guard<std::mutex> lock(mutex_);
if (config_.daily_limit <= 0) {
return 0;
// daily_limit <= 0 means "no daily cap" (see rate_limit.hpp). Signal
// unlimited headroom — NOT 0, which callers (CDOClient::do_get) treat
// as quota-exhausted and would reject every request when the cap is
// intentionally disabled.
return std::numeric_limits<std::int32_t>::max();
}
return config_.daily_limit - daily_requests_used_;
}
Expand Down
18 changes: 16 additions & 2 deletions src/http/client.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -89,13 +89,27 @@ Result<HttpResponse> HttpClient::get(std::string_view path) const {
}

// Set Accept header and extra headers
// curl_slist_append returns nullptr on allocation failure WITHOUT freeing
// the existing list — assigning its result straight back to `headers` would
// then leak the prior nodes and silently drop headers. Capture, check, free
// + error on failure.
struct curl_slist* headers = nullptr;
std::string accept_header = "Accept: " + impl_->config.accept;
headers = curl_slist_append(headers, accept_header.c_str());
struct curl_slist* appended = curl_slist_append(headers, accept_header.c_str());
if (appended == nullptr) {
curl_slist_free_all(headers);
return std::unexpected(Error::network("failed to build request headers"));
}
headers = appended;

for (const std::pair<std::string, std::string>& hdr : impl_->config.extra_headers) {
std::string header_line = hdr.first + ": " + hdr.second;
headers = curl_slist_append(headers, header_line.c_str());
appended = curl_slist_append(headers, header_line.c_str());
if (appended == nullptr) {
curl_slist_free_all(headers);
return std::unexpected(Error::network("failed to build request headers"));
}
headers = appended;
}

curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
Expand Down
8 changes: 8 additions & 0 deletions tests/test_rate_limit.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
#include "ncei/rate_limit.hpp"

#include <cstdint>
#include <gtest/gtest.h>
#include <limits>

namespace ncei {
namespace {
Expand Down Expand Up @@ -127,6 +129,12 @@ TEST(RateLimiterTest, NoDailyLimitWhenZero) {
EXPECT_TRUE(limiter.try_acquire());
EXPECT_TRUE(limiter.try_acquire());
EXPECT_TRUE(limiter.try_acquire());

// Regression: a disabled daily limit must report "unlimited" headroom, NOT
// 0. CDOClient::do_get() rejects with quota_exceeded when remaining <= 0, so
// returning 0 here would block every request whenever the cap is disabled.
EXPECT_GT(limiter.daily_requests_remaining(), 0);
EXPECT_EQ(limiter.daily_requests_remaining(), std::numeric_limits<std::int32_t>::max());
}

TEST(RateLimiterTest, DailyCounterResetsOnReset) {
Expand Down
Loading