Skip to content

Commit dcde564

Browse files
rustam-gamidov-hereasopov-here
authored andcommitted
Introduce ErrorCode::NoContent (#1665)
Allows to distinguish 404 network error and fact that there's no data for the requested resource. This allows to cache NoContent responses and decrease number of such requests Relates-To: HERESUP-50889 Signed-off-by: Rustam Gamidov <ext-rustam.gamidov@here.com>
1 parent 84653b4 commit dcde564

8 files changed

Lines changed: 282 additions & 30 deletions

File tree

olp-cpp-sdk-core/include/olp/core/client/ErrorCode.h

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2019-2025 HERE Europe B.V.
2+
* Copyright (C) 2019-2026 HERE Europe B.V.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -101,6 +101,11 @@ enum class ErrorCode {
101101
* Absence of network connectivity.
102102
*/
103103
Offline,
104+
105+
/**
106+
* The requested content does not exist in the requested resource.
107+
*/
108+
NoContent,
104109
};
105110

106111
} // namespace client

olp-cpp-sdk-dataservice-read/src/VersionedLayerClientImpl.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2019-2025 HERE Europe B.V.
2+
* Copyright (C) 2019-2026 HERE Europe B.V.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -341,8 +341,8 @@ client::CancellationToken VersionedLayerClientImpl::PrefetchPartitions(
341341
auto download = [=](std::string data_handle,
342342
client::CancellationContext inner_context) mutable {
343343
if (data_handle.empty()) {
344-
return BlobApi::DataResponse(
345-
client::ApiError(client::ErrorCode::NotFound, "Not found"));
344+
return BlobApi::DataResponse(client::ApiError(
345+
client::ErrorCode::NoContent, "Partition is empty"));
346346
}
347347
repository::DataCacheRepository data_cache_repository(catalog_,
348348
settings_.cache);
@@ -533,7 +533,7 @@ client::CancellationToken VersionedLayerClientImpl::PrefetchTiles(
533533
client::CancellationContext inner_context) mutable {
534534
if (data_handle.empty()) {
535535
return BlobApi::DataResponse(
536-
ApiError(ErrorCode::NotFound, "Not found"));
536+
ApiError(ErrorCode::NoContent, "Partition is empty"));
537537
}
538538

539539
repository::DataCacheRepository cache(catalog_,

olp-cpp-sdk-dataservice-read/src/VolatileLayerClientImpl.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2019-2024 HERE Europe B.V.
2+
* Copyright (C) 2019-2026 HERE Europe B.V.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -251,8 +251,8 @@ client::CancellationToken VolatileLayerClientImpl::PrefetchTiles(
251251
auto download = [=](std::string data_handle,
252252
client::CancellationContext inner_context) mutable {
253253
if (data_handle.empty()) {
254-
return BlobApi::DataResponse(
255-
client::ApiError(client::ErrorCode::NotFound, "Not found"));
254+
return BlobApi::DataResponse(client::ApiError(
255+
client::ErrorCode::NoContent, "Partition is empty"));
256256
}
257257
repository::DataCacheRepository data_cache_repository(
258258
catalog_, settings_.cache);

olp-cpp-sdk-dataservice-read/src/repositories/DataRepository.cpp

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2019-2024 HERE Europe B.V.
2+
* Copyright (C) 2019-2026 HERE Europe B.V.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -123,8 +123,9 @@ BlobApi::DataResponse DataRepository::GetVersionedData(
123123
catalog_.ToCatalogHRNString().c_str(),
124124
request.CreateKey(layer_id, version).c_str());
125125

126-
return DataResponse(client::ApiError::NotFound("Partition not found"),
127-
network_statistics);
126+
return DataResponse(
127+
client::ApiError(client::ErrorCode::NoContent, "Partition is empty"),
128+
network_statistics);
128129
}
129130

130131
partition = std::move(partitions.front());
@@ -281,7 +282,8 @@ BlobApi::DataResponse DataRepository::GetVolatileData(
281282
catalog_.ToCatalogHRNString().c_str(),
282283
request.CreateKey(layer_id, olp::porting::none).c_str());
283284

284-
return client::ApiError::NotFound("Partition not found");
285+
return client::ApiError(client::ErrorCode::NoContent,
286+
"Partition is empty");
285287
}
286288

287289
partition = std::move(partitions.front());

olp-cpp-sdk-dataservice-read/src/repositories/PartitionsRepository.cpp

Lines changed: 64 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2019-2024 HERE Europe B.V.
2+
* Copyright (C) 2019-2026 HERE Europe B.V.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,6 +20,7 @@
2020
#include "PartitionsRepository.h"
2121

2222
#include <algorithm>
23+
#include <memory>
2324
#include <utility>
2425

2526
#include <olp/core/cache/KeyGenerator.h>
@@ -169,6 +170,36 @@ bool CheckAdditionalFields(
169170

170171
return true;
171172
}
173+
174+
model::Partitions CreateNoContentPartitions(
175+
const std::vector<std::string>& partition_ids) {
176+
model::Partitions result;
177+
auto& partitions = result.GetMutablePartitions();
178+
partitions.reserve(partition_ids.size());
179+
180+
std::transform(partition_ids.cbegin(), partition_ids.cend(),
181+
std::back_inserter(partitions),
182+
[&](const std::string& partition_id) {
183+
// Partition can't be empty, see `Partition::GetPartition`.
184+
// Data handle is the only not optional field and actual data
185+
// is requested by the data handle so not setting it is a
186+
// sign that `Partition` object has no corresponding data.
187+
model::Partition partition;
188+
partition.SetPartition(partition_id);
189+
return partition;
190+
});
191+
192+
return result;
193+
}
194+
195+
bool HasNoContent(const model::Partition& partition) {
196+
// There's client code that caches mocked partitions with zero size for not
197+
// existing data so it's better to check all fields.
198+
return partition.GetDataHandle().empty() && !partition.GetDataSize() &&
199+
!partition.GetCompressedDataSize() && !partition.GetChecksum() &&
200+
!partition.GetCrc() && !partition.GetVersion();
201+
}
202+
172203
} // namespace
173204

174205
namespace olp {
@@ -252,6 +283,16 @@ PartitionsRepository::GetPartitionsExtendedResponse(
252283
OLP_SDK_LOG_TRACE_F(kLogTag,
253284
"GetPartitions found in cache, hrn='%s', key='%s'",
254285
catalog_str.c_str(), key.c_str());
286+
287+
// Clear from cached NoContent partitions
288+
auto& mutable_partitions = cached_partitions->GetMutablePartitions();
289+
mutable_partitions.erase(
290+
std::remove_if(mutable_partitions.begin(), mutable_partitions.end(),
291+
[](const model::Partition& partition) {
292+
return HasNoContent(partition);
293+
}),
294+
mutable_partitions.end());
295+
255296
return *cached_partitions;
256297
} else if (fetch_option == CacheOnly) {
257298
OLP_SDK_LOG_TRACE_F(
@@ -302,8 +343,13 @@ PartitionsRepository::GetPartitionsExtendedResponse(
302343
OLP_SDK_LOG_TRACE_F(kLogTag,
303344
"GetPartitions put to cache, hrn='%s', key='%s'",
304345
catalog_str.c_str(), key.c_str());
346+
305347
const auto put_result =
306-
cache_.Put(response.GetResult(), version, expiry, is_layer_metadata);
348+
cache_.Put(!response.GetResult().GetPartitions().empty()
349+
? response.GetResult()
350+
: CreateNoContentPartitions(partition_ids),
351+
version, expiry, is_layer_metadata);
352+
307353
if (!put_result.IsSuccessful() && fail_on_cache_error) {
308354
OLP_SDK_LOG_ERROR_F(kLogTag,
309355
"Failed to write data to cache, hrn='%s', key='%s'",
@@ -356,6 +402,16 @@ PartitionsResponse PartitionsRepository::GetPartitionById(
356402
OLP_SDK_LOG_TRACE_F(kLogTag,
357403
"GetPartitionById found in cache, hrn='%s', key='%s'",
358404
catalog_.ToCatalogHRNString().c_str(), key.c_str());
405+
406+
// Clear from cached NoContent partitions
407+
auto& mutable_partitions = cached_partitions.GetMutablePartitions();
408+
mutable_partitions.erase(
409+
std::remove_if(mutable_partitions.begin(), mutable_partitions.end(),
410+
[](const model::Partition& partition) {
411+
return HasNoContent(partition);
412+
}),
413+
mutable_partitions.end());
414+
359415
return cached_partitions;
360416
} else if (fetch_option == CacheOnly) {
361417
OLP_SDK_LOG_TRACE_F(
@@ -383,8 +439,13 @@ PartitionsResponse PartitionsRepository::GetPartitionById(
383439
OLP_SDK_LOG_TRACE_F(kLogTag,
384440
"GetPartitionById put to cache, hrn='%s', key='%s'",
385441
catalog_.ToCatalogHRNString().c_str(), key.c_str());
442+
386443
const auto put_result =
387-
cache_.Put(query_response.GetResult(), version, olp::porting::none);
444+
cache_.Put(!query_response.GetResult().GetPartitions().empty()
445+
? query_response.GetResult()
446+
: CreateNoContentPartitions(partitions),
447+
version, olp::porting::none);
448+
388449
if (!put_result.IsSuccessful()) {
389450
OLP_SDK_LOG_ERROR_F(kLogTag,
390451
"GetPartitionById failed to write data to cache, "

olp-cpp-sdk-dataservice-read/tests/PartitionsRepositoryTest.cpp

Lines changed: 146 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright (C) 2019-2025 HERE Europe B.V.
2+
* Copyright (C) 2019-2026 HERE Europe B.V.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -144,6 +144,10 @@ const std::string kHttpResponseLookupQuery =
144144
const std::string kUrlQueryApi =
145145
R"(https://sab.query.data.api.platform.here.com/query/v1/catalogs/hrn:here:data::olp-here-test:hereos-internal-test-v2)";
146146

147+
const std::string kUrlQueryVersionedPartition =
148+
kUrlQueryApi +
149+
R"(/layers/testlayer/partitions?partition=1111&version=100)";
150+
147151
const std::string kQueryTreeIndexWithAdditionalFields =
148152
R"(https://sab.query.data.api.platform.here.com/query/v1/catalogs/hrn:here:data::olp-here-test:hereos-internal-test-v2/layers/testlayer/versions/100/quadkeys/23064/depths/4?additionalFields=)" +
149153
olp::utils::Url::Encode(R"(checksum,crc,dataSize,compressedDataSize)");
@@ -592,9 +596,6 @@ TEST_F(PartitionsRepositoryTest, GetPartitionById) {
592596
TEST_F(PartitionsRepositoryTest, GetVersionedPartitions) {
593597
using testing::Return;
594598

595-
std::shared_ptr<cache::KeyValueCache> default_cache =
596-
client::OlpClientSettingsFactory::CreateDefaultCache({});
597-
598599
auto mock_network = std::make_shared<NetworkMock>();
599600
auto cache = std::make_shared<testing::StrictMock<CacheMock>>();
600601
const auto catalog = HRN::FromString(kCatalog);
@@ -641,6 +642,147 @@ TEST_F(PartitionsRepositoryTest, GetVersionedPartitions) {
641642
ASSERT_FALSE(response.IsSuccessful());
642643
EXPECT_TRUE(response.GetResult().GetPartitions().empty());
643644
}
645+
{
646+
SCOPED_TRACE(
647+
"Succeeds the cache look up when one of the partitions has no content");
648+
OlpClientSettings settings;
649+
settings.cache = cache;
650+
settings.network_request_handler = mock_network;
651+
settings.retry_settings.timeout = 1;
652+
653+
const std::string cache_key_1 =
654+
kCatalog + "::" + kVersionedLayerId + "::" + kPartitionId +
655+
"::" + std::to_string(kVersion) + "::partition";
656+
657+
const std::string cache_key_2 =
658+
kCatalog + "::" + kVersionedLayerId + "::" + kInvalidPartitionId +
659+
"::" + std::to_string(kVersion) + "::partition";
660+
661+
const std::string query_cache_response =
662+
R"jsonString({"version":100,"partition":"1111","layer":"testlayer","dataHandle":"qwerty"})jsonString";
663+
664+
const std::string no_content_cache_response =
665+
R"jsonString({"partition":"2222"})jsonString";
666+
667+
auto response_data = std::make_shared<cache::KeyValueCache::ValueType>(
668+
query_cache_response.begin(), query_cache_response.end());
669+
670+
EXPECT_CALL(*cache, Read(cache_key_1)).WillOnce(Return(response_data));
671+
672+
EXPECT_CALL(*cache, Read(cache_key_2))
673+
.WillOnce(Return(std::make_shared<cache::KeyValueCache::ValueType>(
674+
no_content_cache_response.cbegin(),
675+
no_content_cache_response.cend())));
676+
677+
client::CancellationContext context;
678+
ApiLookupClient lookup_client(catalog, settings);
679+
repository::PartitionsRepository repository(catalog, kVersionedLayerId,
680+
settings, lookup_client);
681+
682+
read::PartitionsRequest request;
683+
request.WithPartitionIds({kPartitionId, kInvalidPartitionId});
684+
request.WithFetchOption(read::CacheOnly);
685+
686+
auto response = repository.GetVersionedPartitionsExtendedResponse(
687+
request, kVersion, context);
688+
689+
ASSERT_TRUE(response.IsSuccessful());
690+
EXPECT_EQ(response.GetResult().GetPartitions().size(), 1);
691+
}
692+
{
693+
SCOPED_TRACE("Cache utilised for not existing partitions");
694+
695+
OlpClientSettings settings;
696+
settings.cache = cache;
697+
settings.network_request_handler = mock_network;
698+
settings.retry_settings.timeout = 1;
699+
700+
const std::string cache_key_1 =
701+
kCatalog + "::" + kVersionedLayerId + "::" + kPartitionId +
702+
"::" + std::to_string(kVersion) + "::partition";
703+
704+
EXPECT_CALL(*cache, Read(cache_key_1))
705+
.WillOnce(Return(client::ApiError::NotFound()));
706+
707+
EXPECT_CALL(*cache, Write(_, _, _)).WillOnce(Return(client::ApiNoResult()));
708+
709+
EXPECT_CALL(*cache, Get(kCacheKeyMetadata, _))
710+
.WillOnce(Return(kUrlQueryApi));
711+
712+
EXPECT_CALL(*mock_network,
713+
Send(IsGetRequest(kUrlQueryVersionedPartition), _, _, _, _))
714+
.WillOnce(ReturnHttpResponse(olp::http::NetworkResponse().WithStatus(
715+
olp::http::HttpStatusCode::OK),
716+
kOlpSdkHttpResponseEmptyPartitionList));
717+
718+
client::CancellationContext context;
719+
ApiLookupClient lookup_client(catalog, settings);
720+
repository::PartitionsRepository repository(catalog, kVersionedLayerId,
721+
settings, lookup_client);
722+
723+
read::PartitionsRequest request;
724+
request.WithPartitionIds({kPartitionId});
725+
request.WithFetchOption(read::OnlineIfNotFound);
726+
727+
auto response = repository.GetVersionedPartitionsExtendedResponse(
728+
request, kVersion, context);
729+
730+
ASSERT_TRUE(response.IsSuccessful());
731+
EXPECT_TRUE(response.GetResult().GetPartitions().empty());
732+
}
733+
{
734+
SCOPED_TRACE("Cache utilised for valid partition");
735+
736+
OlpClientSettings settings;
737+
settings.cache = cache;
738+
settings.network_request_handler = mock_network;
739+
settings.retry_settings.timeout = 1;
740+
741+
const std::string cache_key_1 =
742+
kCatalog + "::" + kVersionedLayerId + "::" + kPartitionId +
743+
"::" + std::to_string(kVersion) + "::partition";
744+
745+
EXPECT_CALL(*cache, Read(cache_key_1))
746+
.WillOnce(Return(client::ApiError::NotFound()));
747+
748+
EXPECT_CALL(*cache, Write(_, _, _))
749+
.Times(4)
750+
.WillRepeatedly(Return(client::ApiNoResult()));
751+
752+
EXPECT_CALL(*cache, Get(kCacheKeyMetadata, _))
753+
.WillOnce(Return(kUrlQueryApi));
754+
755+
EXPECT_CALL(*mock_network,
756+
Send(IsGetRequest(kUrlQueryVersionedPartition), _, _, _, _))
757+
.WillOnce(ReturnHttpResponse(olp::http::NetworkResponse().WithStatus(
758+
olp::http::HttpStatusCode::OK),
759+
kOlpSdkHttpResponsePartitions));
760+
761+
client::CancellationContext context;
762+
ApiLookupClient lookup_client(catalog, settings);
763+
repository::PartitionsRepository repository(catalog, kVersionedLayerId,
764+
settings, lookup_client);
765+
766+
read::PartitionsRequest request;
767+
request.WithPartitionIds({kPartitionId});
768+
request.WithFetchOption(read::OnlineIfNotFound);
769+
770+
auto response = repository.GetVersionedPartitionsExtendedResponse(
771+
request, kVersion, context);
772+
773+
ASSERT_TRUE(response.IsSuccessful());
774+
EXPECT_EQ(response.GetResult().GetPartitions().size(), 4);
775+
}
776+
}
777+
778+
TEST_F(PartitionsRepositoryTest, GetVersionedPartitions_WithCache) {
779+
using testing::Return;
780+
781+
auto mock_network = std::make_shared<NetworkMock>();
782+
const auto catalog = HRN::FromString(kCatalog);
783+
std::shared_ptr<cache::KeyValueCache> default_cache =
784+
client::OlpClientSettingsFactory::CreateDefaultCache({});
785+
644786
{
645787
SCOPED_TRACE("Successful fetch from network with a list of partitions");
646788

0 commit comments

Comments
 (0)