diff --git a/cpp/arcticdb/storage/azure/azure_client_wrapper.hpp b/cpp/arcticdb/storage/azure/azure_client_wrapper.hpp index 2aaec0fc35..bfae88ae64 100644 --- a/cpp/arcticdb/storage/azure/azure_client_wrapper.hpp +++ b/cpp/arcticdb/storage/azure/azure_client_wrapper.hpp @@ -18,6 +18,31 @@ namespace arcticdb::storage::azure { static const size_t BATCH_SUBREQUEST_LIMIT = 256; //https://github.com/Azure/azure-sdk-for-python/blob/767facc39f2487504bcde4e627db16a79f96b297/sdk/storage/azure-storage-blob/azure/storage/blob/_container_client.py#L1608 +// some common error codes as per https://learn.microsoft.com/en-us/rest/api/storageservices/blob-service-error-codes +enum class AzureErrorCode { + BlobAlreadyExists, + BlobNotFound, + ContainerNotFound, + BlobOperationNotSupported, + UnauthorizedBlobOverwrite, + InvalidBlobOrBlock, + OtherError +}; + +inline std::string AzureErrorCode_to_string(AzureErrorCode error) { + switch (error) { + case AzureErrorCode::BlobAlreadyExists: return "BlobAlreadyExists"; + case AzureErrorCode::BlobNotFound: return "BlobNotFound"; + case AzureErrorCode::ContainerNotFound: return "ContainerNotFound"; + case AzureErrorCode::BlobOperationNotSupported: return "BlobOperationNotSupported"; + case AzureErrorCode::UnauthorizedBlobOverwrite: return "UnauthorizedBlobOverwrite"; + case AzureErrorCode::InvalidBlobOrBlock: return "InvalidBlobOrBlock"; + case AzureErrorCode::OtherError: return "Other Unspecified error"; + } + + return "Other Unspecified error"; +} + // An abstract class, which is responsible for sending the requests and parsing the responses from Azure. // It can be derived as either a real connection to Azure or a mock used for unit tests. class AzureClientWrapper { diff --git a/cpp/arcticdb/storage/azure/azure_mock_client.cpp b/cpp/arcticdb/storage/azure/azure_mock_client.cpp index ef848f3d48..7142294052 100644 --- a/cpp/arcticdb/storage/azure/azure_mock_client.cpp +++ b/cpp/arcticdb/storage/azure/azure_mock_client.cpp @@ -30,13 +30,16 @@ std::string operation_to_string(AzureOperation operation){ std::string MockAzureClient::get_failure_trigger( const std::string& blob_name, AzureOperation operation_to_fail, + const std::string& error_code, Azure::Core::Http::HttpStatusCode error_to_fail_with) { - return fmt::format("{}#Failure_{}_{}", blob_name, operation_to_string(operation_to_fail), (int)error_to_fail_with); + return fmt::format("{}#Failure_{}_{}_{}", blob_name, operation_to_string(operation_to_fail), error_code, (int)error_to_fail_with); } -Azure::Core::RequestFailedException get_exception(const std::string& message, Azure::Core::Http::HttpStatusCode status_code) { +Azure::Core::RequestFailedException get_exception(const std::string& message, const std::string& error_code, Azure::Core::Http::HttpStatusCode status_code) { auto rawResponse = std::make_unique(0, 0, status_code, message); + rawResponse->SetHeader("x-ms-error-code", error_code); auto exception = Azure::Core::RequestFailedException(rawResponse); + exception.ErrorCode = error_code; return exception; } @@ -47,11 +50,14 @@ std::optional has_failure_trigger(const std if (position == std::string::npos) return std::nullopt; try { - auto failure_code_string = blob_name.substr(position + failure_string_for_operation.size()); - auto status_code = Azure::Core::Http::HttpStatusCode(std::stoi(failure_code_string)); - auto error_message = fmt::format("Simulated Error, message: #{}_{}", failure_string_for_operation, (int) status_code); - - return get_exception(error_message, status_code); + auto start = position + failure_string_for_operation.size(); + auto error_code = blob_name.substr(start, blob_name.find_last_of('_') - start); + auto status_code_string = blob_name.substr(blob_name.find_last_of('_') + 1); + auto status_code = Azure::Core::Http::HttpStatusCode(std::stoi(status_code_string)); + auto error_message = fmt::format("Simulated Error, message: operation {}, error code {} statuscode {}", + operation_to_string(operation), error_code, (int) status_code); + + return get_exception(error_message, error_code, status_code); } catch (std::exception&) { return std::nullopt; } @@ -82,9 +88,10 @@ Segment MockAzureClient::read_blob( } auto pos = azure_contents.find(blob_name); - if (pos == azure_contents.end()){ - std::string message = fmt::format("Simulated Error, message: #{}_{}", "Read", (int) Azure::Core::Http::HttpStatusCode::NotFound); - throw get_exception(message, Azure::Core::Http::HttpStatusCode::NotFound); + if (pos == azure_contents.end()) { + auto error_code = AzureErrorCode_to_string(AzureErrorCode::BlobNotFound); + std::string message = fmt::format("Simulated Error, message: Read failed {} {}", error_code, (int) Azure::Core::Http::HttpStatusCode::NotFound); + throw get_exception(message, error_code, Azure::Core::Http::HttpStatusCode::NotFound); } return pos->second; diff --git a/cpp/arcticdb/storage/azure/azure_mock_client.hpp b/cpp/arcticdb/storage/azure/azure_mock_client.hpp index dc8f5fd430..ed7bc3f977 100644 --- a/cpp/arcticdb/storage/azure/azure_mock_client.hpp +++ b/cpp/arcticdb/storage/azure/azure_mock_client.hpp @@ -51,6 +51,7 @@ class MockAzureClient : public AzureClientWrapper { static std::string get_failure_trigger( const std::string& blob_name, AzureOperation operation_to_fail, + const std::string& error_code, Azure::Core::Http::HttpStatusCode error_to_fail_with); private: diff --git a/cpp/arcticdb/storage/azure/azure_storage.cpp b/cpp/arcticdb/storage/azure/azure_storage.cpp index 78e3ea7072..8f218bb8f4 100644 --- a/cpp/arcticdb/storage/azure/azure_storage.cpp +++ b/cpp/arcticdb/storage/azure/azure_storage.cpp @@ -47,6 +47,60 @@ namespace fg = folly::gen; namespace detail { +// TODO: fix this temporary workaround to read error code. azure-sdk-cpp client sometimes doesn't properly set the error code. +// This issue has been raised on the sdk repo https://github.com/Azure/azure-sdk-for-cpp/issues/5369. Once fixed, we should no longer need the following function and would just read e.ErrorCode. +std::string get_error_code(const Azure::Core::RequestFailedException& e) { + auto error_code = e.ErrorCode; + + if(error_code.empty() && e.RawResponse ) { + auto headers_map = e.RawResponse->GetHeaders(); + if(auto ec = headers_map.find("x-ms-error-code") ; ec != headers_map.end()) { + error_code = ec->second; + } + } + return error_code; +} + +void raise_azure_exception(const Azure::Core::RequestFailedException& e) { + auto error_code = get_error_code(e); + auto status_code = e.StatusCode; + std::string error_message; + + if(status_code == Azure::Core::Http::HttpStatusCode::NotFound && error_code == AzureErrorCode_to_string(AzureErrorCode::BlobNotFound)) { + throw KeyNotFoundException(fmt::format("Key Not Found Error: AzureError#{} {}: {}", + static_cast(status_code), error_code, e.ReasonPhrase)); + } + + if(status_code == Azure::Core::Http::HttpStatusCode::Unauthorized || status_code == Azure::Core::Http::HttpStatusCode::Forbidden) { + raise(fmt::format("Permission error: AzureError#{} {}: {}", + static_cast(status_code), error_code, e.ReasonPhrase)); + } + + if(static_cast(status_code) >= 500) { + error_message = fmt::format("Unexpected Server Error: AzureError#{} {}: {} {}", + static_cast(status_code), error_code, e.ReasonPhrase, e.what()); + } else { + error_message = fmt::format("Unexpected Error: AzureError#{} {}: {} {}", + static_cast(status_code), error_code, e.ReasonPhrase, e.what()); + } + + raise(error_message); +} + +bool is_expected_error_type(const std::string& error_code, Azure::Core::Http::HttpStatusCode status_code) { + return status_code == Azure::Core::Http::HttpStatusCode::NotFound && (error_code == AzureErrorCode_to_string(AzureErrorCode::BlobNotFound) || + error_code == AzureErrorCode_to_string(AzureErrorCode::ContainerNotFound)); +} + +void raise_if_unexpected_error(const Azure::Core::RequestFailedException& e) { + auto error_code = get_error_code(e); + auto status_code = e.StatusCode; + + if(!is_expected_error_type(error_code, status_code)) { + raise_azure_exception(e); + } +} + template void do_write_impl( Composite&& kvs, @@ -72,12 +126,8 @@ void do_write_impl( try { azure_client.write_blob(blob_name, std::move(seg), upload_option, request_timeout); } - catch (const Azure::Core::RequestFailedException& e){ - util::raise_rte("Failed to write azure with key '{}' {} {}: {}", - k, - blob_name, - static_cast(e.StatusCode), - e.ReasonPhrase); + catch (const Azure::Core::RequestFailedException& e) { + raise_azure_exception(e); } } @@ -118,12 +168,13 @@ void do_read_impl(Composite && ks, for (auto& k : group.values()) { auto key_type_dir = key_type_folder(root_folder, variant_key_type(k)); auto blob_name = object_path(b.bucketize(key_type_dir, k), k); - try{ + try { visitor(k, azure_client.read_blob(blob_name, download_option, request_timeout)); ARCTICDB_DEBUG(log::storage(), "Read key {}: {}", variant_key_type(k), variant_key_view(k)); } - catch (const Azure::Core::RequestFailedException& e){ + catch (const Azure::Core::RequestFailedException& e) { + raise_if_unexpected_error(e); if (!opts.dont_warn_about_missing_key) { log::storage().warn("Failed to read azure segment with key '{}' {} {}: {}", k, @@ -157,9 +208,7 @@ void do_remove_impl(Composite&& ks, azure_client.delete_blobs(to_delete, request_timeout); } catch (const Azure::Core::RequestFailedException& e) { - util::raise_rte("Failed to create azure segment batch remove request {}: {}", - static_cast(e.StatusCode), - e.ReasonPhrase); + raise_azure_exception(e); } batch_counter = 0u; }; @@ -220,6 +269,7 @@ void do_iterate_type_impl(KeyType key_type, } } catch (const Azure::Core::RequestFailedException& e) { + raise_if_unexpected_error(e); log::storage().warn("Failed to iterate azure blobs '{}' {}: {}", key_type, static_cast(e.StatusCode), @@ -235,10 +285,11 @@ bool do_key_exists_impl( KeyBucketizer&& bucketizer) { auto key_type_dir = key_type_folder(root_folder, variant_key_type(key)); auto blob_name = object_path(bucketizer.bucketize(key_type_dir, key), key); - try{ + try { return azure_client.blob_exists(blob_name); } - catch (const Azure::Core::RequestFailedException& e){ + catch (const Azure::Core::RequestFailedException& e) { + raise_if_unexpected_error(e); log::storage().debug("Failed to check azure key '{}' {} {}: {}", key, blob_name, diff --git a/cpp/arcticdb/storage/s3/s3_mock_client.cpp b/cpp/arcticdb/storage/s3/s3_mock_client.cpp index a0fb57a3b1..f5f416f503 100644 --- a/cpp/arcticdb/storage/s3/s3_mock_client.cpp +++ b/cpp/arcticdb/storage/s3/s3_mock_client.cpp @@ -48,7 +48,8 @@ std::optional has_failure_trigger(const std::string& s3_object auto position = s3_object_name.rfind(failure_string_for_operation); if (position == std::string::npos) return std::nullopt; try { - auto failure_code_string = s3_object_name.substr(position + failure_string_for_operation.size(), s3_object_name.find_last_of('_')); + auto start = position + failure_string_for_operation.size(); + auto failure_code_string = s3_object_name.substr(start, s3_object_name.find_last_of('_') - start); auto failure_code = Aws::S3::S3Errors(std::stoi(failure_code_string)); bool retryable = std::stoi(s3_object_name.substr(s3_object_name.find_last_of('_') + 1)); return Aws::S3::S3Error(Aws::Client::AWSError(failure_code, "Simulated error", "Simulated error message", retryable)); diff --git a/cpp/arcticdb/storage/test/common.hpp b/cpp/arcticdb/storage/test/common.hpp index 0f885c5e12..ac94e0bee5 100644 --- a/cpp/arcticdb/storage/test/common.hpp +++ b/cpp/arcticdb/storage/test/common.hpp @@ -30,6 +30,11 @@ inline void write_in_store(Storage &store, std::string symbol) { store.write(KeySegmentPair(std::move(variant_key), get_test_segment())); } +inline void update_in_store(Storage &store, std::string symbol) { + auto variant_key = get_test_key(symbol); + store.update(KeySegmentPair(std::move(variant_key), get_test_segment()), arcticdb::storage::UpdateOpts{}); +} + inline bool exists_in_store(Storage &store, std::string symbol) { auto variant_key = get_test_key(symbol); return store.key_exists(variant_key); diff --git a/cpp/arcticdb/storage/test/test_azure_storage.cpp b/cpp/arcticdb/storage/test/test_azure_storage.cpp index 5293095291..20933b5b37 100644 --- a/cpp/arcticdb/storage/test/test_azure_storage.cpp +++ b/cpp/arcticdb/storage/test/test_azure_storage.cpp @@ -48,8 +48,9 @@ TEST_F(AzureMockStorageFixture, test_read){ TEST_F(AzureMockStorageFixture, test_write){ write_in_store(store, "symbol"); ASSERT_THROW( - write_in_store(store, MockAzureClient::get_failure_trigger("symbol", AzureOperation::WRITE, Azure::Core::Http::HttpStatusCode::Unauthorized)), - arcticdb::ArcticException); + write_in_store(store, MockAzureClient::get_failure_trigger("symbol", + AzureOperation::WRITE, AzureErrorCode_to_string(AzureErrorCode::UnauthorizedBlobOverwrite), + Azure::Core::Http::HttpStatusCode::Unauthorized)),arcticdb::ArcticException); } TEST_F(AzureMockStorageFixture, test_remove) { diff --git a/cpp/arcticdb/storage/test/test_storage_exceptions.cpp b/cpp/arcticdb/storage/test/test_storage_exceptions.cpp index 60b7f7f373..5ef5615991 100644 --- a/cpp/arcticdb/storage/test/test_storage_exceptions.cpp +++ b/cpp/arcticdb/storage/test/test_storage_exceptions.cpp @@ -13,6 +13,9 @@ #include #include #include +#include +#include +#include #include #include @@ -51,7 +54,7 @@ class LMDBStorageFactory : public StorageFactory { arcticdb::storage::LibraryPath library_path{"a", "b"}; - return std::make_unique(library_path, arcticdb::storage::OpenMode::WRITE, cfg); + return std::make_unique(library_path, arcticdb::storage::OpenMode::DELETE, cfg); } void setup() override { @@ -73,7 +76,7 @@ class MemoryStorageFactory : public StorageFactory { arcticdb::proto::memory_storage::Config cfg; arcticdb::storage::LibraryPath library_path{"a", "b"}; - return std::make_unique(library_path, arcticdb::storage::OpenMode::WRITE, cfg); + return std::make_unique(library_path, arcticdb::storage::OpenMode::DELETE, cfg); } }; @@ -84,7 +87,18 @@ class S3MockStorageFactory : public StorageFactory { cfg.set_use_mock_storage_for_testing(true); arcticdb::storage::LibraryPath library_path("lib", '.'); - return std::make_unique(library_path, arcticdb::storage::OpenMode::WRITE, cfg); + return std::make_unique(library_path, arcticdb::storage::OpenMode::DELETE, cfg); + } +}; + +class AzureMockStorageFactory : public StorageFactory { +public: + std::unique_ptr create() override { + arcticdb::proto::azure_storage::Config cfg; + cfg.set_use_mock_storage_for_testing(true); + arcticdb::storage::LibraryPath library_path("lib", '/'); + + return std::make_unique(library_path,arcticdb::storage::OpenMode::DELETE, cfg); } }; @@ -106,54 +120,35 @@ class GenericStorageTest : public ::testing::TestWithParam("sym"); - - arcticdb::storage::KeySegmentPair kv(k); - kv.segment().set_buffer(std::make_shared()); - - storage->write(std::move(kv)); - - ASSERT_TRUE(storage->key_exists(k)); - - arcticdb::storage::KeySegmentPair kv1(k); - kv1.segment().set_buffer(std::make_shared()); + write_in_store(*storage, "sym"); + ASSERT_TRUE(exists_in_store(*storage, "sym")); ASSERT_THROW({ - storage->write(std::move(kv1)); + write_in_store(*storage, "sym"); }, arcticdb::storage::DuplicateKeyException); } TEST_P(GenericStorageTest, ReadKeyNotFoundException) { - arcticdb::entity::AtomKey k = arcticdb::entity::atom_key_builder().gen_id(0).build("sym"); - - ASSERT_TRUE(!storage->key_exists(k)); + ASSERT_FALSE(exists_in_store(*storage, "sym")); ASSERT_THROW({ - storage->read(k, arcticdb::storage::ReadKeyOpts{}); + read_in_store(*storage, "sym"); }, arcticdb::storage::KeyNotFoundException); } TEST_P(GenericStorageTest, UpdateKeyNotFoundException) { - arcticdb::entity::AtomKey k = arcticdb::entity::atom_key_builder().gen_id(0).build("sym"); - - arcticdb::storage::KeySegmentPair kv(k); - kv.segment().header().set_start_ts(1234); - kv.segment().set_buffer(std::make_shared()); - - ASSERT_TRUE(!storage->key_exists(k)); + ASSERT_FALSE(exists_in_store(*storage, "sym")); ASSERT_THROW({ - storage->update(std::move(kv), arcticdb::storage::UpdateOpts{}); + update_in_store(*storage, "sym"); }, arcticdb::storage::KeyNotFoundException); } TEST_P(GenericStorageTest, RemoveKeyNotFoundException) { - arcticdb::entity::AtomKey k = arcticdb::entity::atom_key_builder().gen_id(0).build("sym"); - - ASSERT_TRUE(!storage->key_exists(k)); + ASSERT_FALSE(exists_in_store(*storage, "sym")); ASSERT_THROW({ - storage->remove(k, arcticdb::storage::RemoveOpts{}); + remove_in_store(*storage, {"sym"}); }, arcticdb::storage::KeyNotFoundException); } @@ -209,11 +204,9 @@ TEST(S3MockStorageTest, TestReadKeyNotFoundException) { S3MockStorageFactory factory; auto storage = factory.create(); - std::string failureSymbol = s3::MockS3Client::get_failure_trigger("sym", s3::S3Operation::GET, Aws::S3::S3Errors::NO_SUCH_KEY); - auto k = entity::atom_key_builder().gen_id(0).build(failureSymbol); - + ASSERT_FALSE(exists_in_store(*storage, "sym")); ASSERT_THROW({ - storage->read(k, storage::ReadKeyOpts{}); + read_in_store(*storage, "sym"); }, arcticdb::storage::KeyNotFoundException); } @@ -224,28 +217,22 @@ TEST(S3MockStorageTest, TestPermissionErrorException) { auto storage = factory.create(); std::string failureSymbol = s3::MockS3Client::get_failure_trigger("sym1", s3::S3Operation::GET, Aws::S3::S3Errors::ACCESS_DENIED); - AtomKeyImpl k = entity::atom_key_builder().gen_id(0).build(failureSymbol); ASSERT_THROW({ - storage->read(k, storage::ReadKeyOpts{}); - }, PermissionException); + read_in_store(*storage, failureSymbol); + }, StoragePermissionException); failureSymbol = s3::MockS3Client::get_failure_trigger("sym2", s3::S3Operation::DELETE, Aws::S3::S3Errors::ACCESS_DENIED); - k = entity::atom_key_builder().gen_id(0).build(failureSymbol); ASSERT_THROW({ - storage->remove(k, storage::RemoveOpts{}); - }, PermissionException); + remove_in_store(*storage, {failureSymbol}); + }, StoragePermissionException); failureSymbol = s3::MockS3Client::get_failure_trigger("sym3", s3::S3Operation::PUT, Aws::S3::S3Errors::INVALID_ACCESS_KEY_ID); - k = entity::atom_key_builder().gen_id(0).build(failureSymbol); - KeySegmentPair kv = KeySegmentPair(k); - kv.segment().header().set_start_ts(1234); - kv.segment().set_buffer(std::make_shared()); ASSERT_THROW({ - storage->update(std::move(kv), storage::UpdateOpts{}); - }, PermissionException); + update_in_store(*storage, failureSymbol); + }, StoragePermissionException); } @@ -254,10 +241,9 @@ TEST(S3MockStorageTest, TestS3RetryableException) { auto storage = factory.create(); std::string failureSymbol = s3::MockS3Client::get_failure_trigger("sym1", s3::S3Operation::GET, Aws::S3::S3Errors::NETWORK_CONNECTION); - AtomKeyImpl k = entity::atom_key_builder().gen_id(0).build(failureSymbol); ASSERT_THROW({ - storage->read(k, storage::ReadKeyOpts{}); + read_in_store(*storage, failureSymbol); }, S3RetryableException); } @@ -266,9 +252,63 @@ TEST(S3MockStorageTest, TestUnexpectedS3ErrorException ) { auto storage = factory.create(); std::string failureSymbol = s3::MockS3Client::get_failure_trigger("sym1", s3::S3Operation::GET, Aws::S3::S3Errors::NETWORK_CONNECTION, false); - AtomKeyImpl k = entity::atom_key_builder().gen_id(0).build(failureSymbol); ASSERT_THROW({ - storage->read(k, storage::ReadKeyOpts{}); + read_in_store(*storage, failureSymbol); }, UnexpectedS3ErrorException); } + +// Azure error testing with mock client +TEST(AzureMockStorageTest, TestReadKeyNotFoundException) { + AzureMockStorageFactory factory; + auto storage = factory.create(); + + ASSERT_FALSE(exists_in_store(*storage, "sym")); + ASSERT_THROW({ + read_in_store(*storage, "sym"); + }, arcticdb::storage::KeyNotFoundException); + +} + +// Check that Permission exception is thrown when http Forbidden status code is returned +TEST(AzureMockStorageTest, TestPermissionErrorException) { + AzureMockStorageFactory factory; + auto storage = factory.create(); + write_in_store(*storage, "sym1"); + + std::string failureSymbol = azure::MockAzureClient::get_failure_trigger("sym1", azure::AzureOperation::WRITE, + azure::AzureErrorCode_to_string(azure::AzureErrorCode::UnauthorizedBlobOverwrite), + Azure::Core::Http::HttpStatusCode::Forbidden); + ASSERT_THROW({ + update_in_store(*storage, failureSymbol); + }, StoragePermissionException); + + failureSymbol = azure::MockAzureClient::get_failure_trigger("sym1", azure::AzureOperation::DELETE, + azure::AzureErrorCode_to_string(azure::AzureErrorCode::UnauthorizedBlobOverwrite), + Azure::Core::Http::HttpStatusCode::Forbidden); + ASSERT_THROW({ + remove_in_store(*storage, {failureSymbol}); + }, StoragePermissionException); + +} + +TEST(AzureMockStorageTest, TestUnexpectedAzureErrorException ) { + AzureMockStorageFactory factory; + auto storage = factory.create(); + + std::string failureSymbol = azure::MockAzureClient::get_failure_trigger("sym1@#~?.&$", azure::AzureOperation::READ, + azure::AzureErrorCode_to_string(azure::AzureErrorCode::InvalidBlobOrBlock), + Azure::Core::Http::HttpStatusCode::BadRequest); + + ASSERT_THROW({ + read_in_store(*storage, failureSymbol); + }, UnexpectedAzureException); + + failureSymbol = azure::MockAzureClient::get_failure_trigger("sym1", azure::AzureOperation::READ, + "", + Azure::Core::Http::HttpStatusCode::InternalServerError); + + ASSERT_THROW({ + read_in_store(*storage, failureSymbol); + }, UnexpectedAzureException); +} diff --git a/cpp/arcticdb/util/error_code.hpp b/cpp/arcticdb/util/error_code.hpp index b83454f2a9..814954f168 100644 --- a/cpp/arcticdb/util/error_code.hpp +++ b/cpp/arcticdb/util/error_code.hpp @@ -82,6 +82,7 @@ inline std::unordered_map get_error_category_names() ERROR_CODE(5004, E_PERMISSION) \ ERROR_CODE(5005, E_UNEXPECTED_S3_ERROR) \ ERROR_CODE(5006, E_S3_RETRYABLE) \ + ERROR_CODE(5007, E_UNEXPECTED_AZURE_ERROR) \ ERROR_CODE(6000, E_UNSORTED_DATA) \ ERROR_CODE(7000, E_INVALID_USER_ARGUMENT) \ ERROR_CODE(7001, E_INVALID_DECIMAL_STRING) \ @@ -155,9 +156,10 @@ using NormalizationException = ArcticCategorizedException; using StorageException = ArcticCategorizedException; using MissingDataException = ArcticCategorizedException; -using PermissionException = ArcticSpecificException; +using StoragePermissionException = ArcticSpecificException; using UnexpectedS3ErrorException = ArcticSpecificException; using S3RetryableException = ArcticSpecificException; +using UnexpectedAzureException = ArcticSpecificException; using SortingException = ArcticCategorizedException; using UnsortedDataException = ArcticSpecificException; using UserInputException = ArcticCategorizedException; @@ -184,6 +186,11 @@ template<> throw ArcticSpecificException(msg); } +template<> +[[noreturn]] inline void throw_error(const std::string& msg) { + throw ArcticSpecificException(msg); +} + template<> [[noreturn]] inline void throw_error(const std::string& msg) { throw ArcticSpecificException(msg); diff --git a/docs/mkdocs/docs/error_messages.md b/docs/mkdocs/docs/error_messages.md index 4cfeabaab4..4a44e9e6c8 100644 --- a/docs/mkdocs/docs/error_messages.md +++ b/docs/mkdocs/docs/error_messages.md @@ -60,6 +60,7 @@ For legacy reasons, the terms `symbol`, `stream`, and `stream ID` are used inter | 5004 | Don't have permissions to carry out the operation. | Ensure that you have the permissions to perform the requested operation on the given key. | | 5005 | An unexpected S3 error occurred. e.g. Network error, Service not available, Throttling failure etc. | Varies depending on the type of failure. | | 5006 | An unexpected S3 error occurred which is retryable. | Varies depending on the type of failure. | +| 5007 | An unexpected Azure error occurred with a given status code and error code. | Varies depending on the type of failure. Read more on [Azure Blob Storage error code docs](https://learn.microsoft.com/en-us/rest/api/storageservices/blob-service-error-codes). | ### Sorting Errors