diff --git a/alternator/auth.cc b/alternator/auth.cc index d2e50de33b..985a0f2894 100644 --- a/alternator/auth.cc +++ b/alternator/auth.cc @@ -78,12 +78,12 @@ void check_expiry(std::string_view signature_date) { std::string expiration_str = format_time_point(db_clock::now() - 15min); std::string validity_str = format_time_point(db_clock::now() + 15min); if (signature_date < expiration_str) { - throw api_error("InvalidSignatureException", + throw api_error::invalid_signature( fmt::format("Signature expired: {} is now earlier than {} (current time - 15 min.)", signature_date, expiration_str)); } if (signature_date > validity_str) { - throw api_error("InvalidSignatureException", + throw api_error::invalid_signature( fmt::format("Signature not yet current: {} is still later than {} (current time + 15 min.)", signature_date, validity_str)); } @@ -94,13 +94,13 @@ std::string get_signature(std::string_view access_key_id, std::string_view secre std::string_view body_content, std::string_view region, std::string_view service, std::string_view query_string) { auto amz_date_it = signed_headers_map.find("x-amz-date"); if (amz_date_it == signed_headers_map.end()) { - throw api_error("InvalidSignatureException", "X-Amz-Date header is mandatory for signature verification"); + throw api_error::invalid_signature("X-Amz-Date header is mandatory for signature verification"); } std::string_view amz_date = amz_date_it->second; check_expiry(amz_date); std::string_view datestamp = amz_date.substr(0, 8); if (datestamp != orig_datestamp) { - throw api_error("InvalidSignatureException", + throw api_error::invalid_signature( format("X-Amz-Date date does not match the provided datestamp. Expected {}, got {}", orig_datestamp, datestamp)); } @@ -134,11 +134,11 @@ future get_key_from_roles(cql3::query_processor& qp, std::string us auto res = f.get0(); auto salted_hash = std::optional(); if (res->empty()) { - throw api_error("UnrecognizedClientException", fmt::format("User not found: {}", username)); + throw api_error::unrecognized_client(fmt::format("User not found: {}", username)); } salted_hash = res->one().get_opt("salted_hash"); if (!salted_hash) { - throw api_error("UnrecognizedClientException", fmt::format("No password found for user: {}", username)); + throw api_error::unrecognized_client(fmt::format("No password found for user: {}", username)); } return make_ready_future(*salted_hash); }); diff --git a/alternator/conditions.cc b/alternator/conditions.cc index d52ada0581..4e6b89706b 100644 --- a/alternator/conditions.cc +++ b/alternator/conditions.cc @@ -57,12 +57,12 @@ comparison_operator_type get_comparison_operator(const rjson::value& comparison_ {"NOT_CONTAINS", comparison_operator_type::NOT_CONTAINS}, }; if (!comparison_operator.IsString()) { - throw api_error("ValidationException", format("Invalid comparison operator definition {}", rjson::print(comparison_operator))); + throw api_error::validation(format("Invalid comparison operator definition {}", rjson::print(comparison_operator))); } std::string op = comparison_operator.GetString(); auto it = ops.find(op); if (it == ops.end()) { - throw api_error("ValidationException", format("Unsupported comparison operator {}", op)); + throw api_error::validation(format("Unsupported comparison operator {}", op)); } return it->second; } @@ -104,10 +104,10 @@ static void verify_operand_count(const rjson::value* array, const size_check& ex return; } if (!array || !array->IsArray()) { - throw api_error("ValidationException", "With ComparisonOperator, AttributeValueList must be given and an array"); + throw api_error::validation("With ComparisonOperator, AttributeValueList must be given and an array"); } if (!expected(array->Size())) { - throw api_error("ValidationException", + throw api_error::validation( format("{} operator requires AttributeValueList {}, instead found list size {}", op, expected.what(), array->Size())); } @@ -164,11 +164,11 @@ static bool check_BEGINS_WITH(const rjson::value* v1, const rjson::value& v2) { // binary - otherwise it's a validation error. However, problems with // the stored attribute (v1) will just return false (no match). if (!v2.IsObject() || v2.MemberCount() != 1) { - throw api_error("ValidationException", format("BEGINS_WITH operator encountered malformed AttributeValue: {}", v2)); + throw api_error::validation(format("BEGINS_WITH operator encountered malformed AttributeValue: {}", v2)); } auto it2 = v2.MemberBegin(); if (it2->name != "S" && it2->name != "B") { - throw api_error("ValidationException", format("BEGINS_WITH operator requires String or Binary type in AttributeValue, got {}", it2->name)); + throw api_error::validation(format("BEGINS_WITH operator requires String or Binary type in AttributeValue, got {}", it2->name)); } @@ -233,12 +233,12 @@ static bool check_NOT_CONTAINS(const rjson::value* v1, const rjson::value& v2) { // Check if a JSON-encoded value equals any element of an array, which must have at least one element. static bool check_IN(const rjson::value* val, const rjson::value& array) { if (!array[0].IsObject() || array[0].MemberCount() != 1) { - throw api_error("ValidationException", + throw api_error::validation( format("IN operator encountered malformed AttributeValue: {}", array[0])); } const auto& type = array[0].MemberBegin()->name; if (type != "S" && type != "N" && type != "B") { - throw api_error("ValidationException", + throw api_error::validation( "IN operator requires AttributeValueList elements to be of type String, Number, or Binary "); } if (!val) { @@ -247,7 +247,7 @@ static bool check_IN(const rjson::value* val, const rjson::value& array) { bool have_match = false; for (const auto& elem : array.GetArray()) { if (!elem.IsObject() || elem.MemberCount() != 1 || elem.MemberBegin()->name != type) { - throw api_error("ValidationException", + throw api_error::validation( "IN operator requires all AttributeValueList elements to have the same type "); } if (!have_match && *val == elem) { @@ -283,13 +283,13 @@ static bool check_NOT_NULL(const rjson::value* val) { template bool check_compare(const rjson::value* v1, const rjson::value& v2, const Comparator& cmp) { if (!v2.IsObject() || v2.MemberCount() != 1) { - throw api_error("ValidationException", + throw api_error::validation( format("{} requires a single AttributeValue of type String, Number, or Binary", cmp.diagnostic)); } const auto& kv2 = *v2.MemberBegin(); if (kv2.name != "S" && kv2.name != "N" && kv2.name != "B") { - throw api_error("ValidationException", + throw api_error::validation( format("{} requires a single AttributeValue of type String, Number, or Binary", cmp.diagnostic)); } @@ -345,7 +345,7 @@ struct cmp_gt { template static bool check_BETWEEN(const T& v, const T& lb, const T& ub) { if (cmp_lt()(ub, lb)) { - throw api_error("ValidationException", + throw api_error::validation( format("BETWEEN operator requires lower_bound <= upper_bound, but {} > {}", lb, ub)); } return cmp_ge()(v, lb) && cmp_le()(v, ub); @@ -356,21 +356,20 @@ static bool check_BETWEEN(const rjson::value* v, const rjson::value& lb, const r return false; } if (!v->IsObject() || v->MemberCount() != 1) { - throw api_error("ValidationException", format("BETWEEN operator encountered malformed AttributeValue: {}", *v)); + throw api_error::validation(format("BETWEEN operator encountered malformed AttributeValue: {}", *v)); } if (!lb.IsObject() || lb.MemberCount() != 1) { - throw api_error("ValidationException", format("BETWEEN operator encountered malformed AttributeValue: {}", lb)); + throw api_error::validation(format("BETWEEN operator encountered malformed AttributeValue: {}", lb)); } if (!ub.IsObject() || ub.MemberCount() != 1) { - throw api_error("ValidationException", format("BETWEEN operator encountered malformed AttributeValue: {}", ub)); + throw api_error::validation(format("BETWEEN operator encountered malformed AttributeValue: {}", ub)); } const auto& kv_v = *v->MemberBegin(); const auto& kv_lb = *lb.MemberBegin(); const auto& kv_ub = *ub.MemberBegin(); if (kv_lb.name != kv_ub.name) { - throw api_error( - "ValidationException", + throw api_error::validation( format("BETWEEN operator requires the same type for lower and upper bound; instead got {} and {}", kv_lb.name, kv_ub.name)); } @@ -389,7 +388,7 @@ static bool check_BETWEEN(const rjson::value* v, const rjson::value& lb, const r if (kv_v.name == "B") { return check_BETWEEN(base64_decode(kv_v.value), base64_decode(kv_lb.value), base64_decode(kv_ub.value)); } - throw api_error("ValidationException", + throw api_error::validation( format("BETWEEN operator requires AttributeValueList elements to be of type String, Number, or Binary; instead got {}", kv_lb.name)); } @@ -409,24 +408,24 @@ static bool verify_expected_one(const rjson::value& condition, const rjson::valu // and requires a different combinations of parameters in the request if (value) { if (exists && (!exists->IsBool() || exists->GetBool() != true)) { - throw api_error("ValidationException", "Cannot combine Value with Exists!=true"); + throw api_error::validation("Cannot combine Value with Exists!=true"); } if (comparison_operator) { - throw api_error("ValidationException", "Cannot combine Value with ComparisonOperator"); + throw api_error::validation("Cannot combine Value with ComparisonOperator"); } return check_EQ(got, *value); } else if (exists) { if (comparison_operator) { - throw api_error("ValidationException", "Cannot combine Exists with ComparisonOperator"); + throw api_error::validation("Cannot combine Exists with ComparisonOperator"); } if (!exists->IsBool() || exists->GetBool() != false) { - throw api_error("ValidationException", "Exists!=false requires Value"); + throw api_error::validation("Exists!=false requires Value"); } // Remember Exists=false, so we're checking that the attribute does *not* exist: return !got; } else { if (!comparison_operator) { - throw api_error("ValidationException", "Missing ComparisonOperator, Value or Exists"); + throw api_error::validation("Missing ComparisonOperator, Value or Exists"); } comparison_operator_type op = get_comparison_operator(*comparison_operator); switch (op) { @@ -471,7 +470,7 @@ static bool verify_expected_one(const rjson::value& condition, const rjson::valu const rjson::value& arg = (*attribute_value_list)[0]; const auto& argtype = (*arg.MemberBegin()).name; if (argtype != "S" && argtype != "N" && argtype != "B") { - throw api_error("ValidationException", + throw api_error::validation( format("CONTAINS operator requires a single AttributeValue of type String, Number, or Binary, " "got {} instead", argtype)); } @@ -485,7 +484,7 @@ static bool verify_expected_one(const rjson::value& condition, const rjson::valu const rjson::value& arg = (*attribute_value_list)[0]; const auto& argtype = (*arg.MemberBegin()).name; if (argtype != "S" && argtype != "N" && argtype != "B") { - throw api_error("ValidationException", + throw api_error::validation( format("CONTAINS operator requires a single AttributeValue of type String, Number, or Binary, " "got {} instead", argtype)); } @@ -502,7 +501,7 @@ conditional_operator_type get_conditional_operator(const rjson::value& req) { return conditional_operator_type::MISSING; } if (!conditional_operator->IsString()) { - throw api_error("ValidationException", "'ConditionalOperator' parameter, if given, must be a string"); + throw api_error::validation("'ConditionalOperator' parameter, if given, must be a string"); } auto s = rjson::to_string_view(*conditional_operator); if (s == "AND") { @@ -510,7 +509,7 @@ conditional_operator_type get_conditional_operator(const rjson::value& req) { } else if (s == "OR") { return conditional_operator_type::OR; } else { - throw api_error("ValidationException", + throw api_error::validation( format("'ConditionalOperator' parameter must be AND, OR or missing. Found {}.", s)); } } @@ -525,13 +524,13 @@ bool verify_expected(const rjson::value& req, const rjson::value* previous_item) auto conditional_operator = get_conditional_operator(req); if (conditional_operator != conditional_operator_type::MISSING && (!expected || (expected->IsObject() && expected->GetObject().ObjectEmpty()))) { - throw api_error("ValidationException", "'ConditionalOperator' parameter cannot be specified for missing or empty Expression"); + throw api_error::validation("'ConditionalOperator' parameter cannot be specified for missing or empty Expression"); } if (!expected) { return true; } if (!expected->IsObject()) { - throw api_error("ValidationException", "'Expected' parameter, if given, must be an object"); + throw api_error::validation("'Expected' parameter, if given, must be an object"); } bool require_all = conditional_operator != conditional_operator_type::OR; return verify_condition(*expected, require_all, previous_item); @@ -589,7 +588,7 @@ static bool calculate_primitive_condition(const parsed::primitive_condition& con return it->value.GetBool(); } } - throw api_error("ValidationException", + throw api_error::validation( format("ConditionExpression: condition results in a non-boolean value: {}", calculated_values[0])); default: diff --git a/alternator/error.cc b/alternator/error.cc deleted file mode 100644 index f5b3299be2..0000000000 --- a/alternator/error.cc +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright 2020 ScyllaDB - */ - -/* - * This file is part of Scylla. - * - * Scylla is free software: you can redistribute it and/or modify - * it under the terms of the GNU Affero General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Scylla is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU Affero General Public License - * along with Scylla. If not, see . - */ - -#include -#include -#include "error.hh" - -std::ostream& alternator::detail::operator<<(std::ostream& os, exception_type t) { - switch (t) { - default: - case exception_type::ValidationException: return os << "ValidationException"; - case exception_type::ResourceNotFoundException: return os << "ResourceNotFoundException"; - case exception_type::AccessDeniedException: return os << "AccessDeniedException"; - case exception_type::InvalidSignatureException: return os << "InvalidSignatureException"; - } -} - -alternator::api_error::api_error(detail::exception_type type, std::string msg, status_type http_code) - : api_error(boost::lexical_cast(type), std::move(msg), http_code) -{} diff --git a/alternator/error.hh b/alternator/error.hh index 5b79842b4e..f4c0d311e7 100644 --- a/alternator/error.hh +++ b/alternator/error.hh @@ -25,23 +25,16 @@ #include "seastarx.hh" namespace alternator { -namespace detail { - enum class exception_type { - ValidationException, - ResourceNotFoundException, - AccessDeniedException, - InvalidSignatureException, - }; - std::ostream& operator<<(std::ostream&, exception_type); -} - -// DynamoDB's error messages are described in detail in +// api_error contains a DynamoDB error message to be returned to the user. +// It can be returned by value (see executor::request_return_type) or thrown. +// The DynamoDB's error messages are described in detail in // https://docs.aws.amazon.com/amazondynamodb/latest/developerguide/Programming.Errors.html -// Ah An error message has a "type", e.g., "ResourceNotFoundException", a coarser -// HTTP code (almost always, 400), and a human readable message. Eventually these -// will be wrapped into a JSON object returned to the client. -class api_error : public std::exception { +// An error message has an HTTP code (almost always 400), a type, e.g., +// "ResourceNotFoundException", and a human readable message. +// Eventually alternator::api_handler will convert a returned or thrown +// api_error into a JSON object, and that is returned to the user. +class api_error final { public: using status_type = httpd::reply::status_type; status_type _http_code; @@ -52,26 +45,36 @@ public: , _type(std::move(type)) , _msg(std::move(msg)) { } - api_error() = default; - virtual const char* what() const noexcept override { return _msg.c_str(); } -protected: - api_error(detail::exception_type, std::string msg, status_type http_code); -}; -namespace detail { -template -class t_api_error : public api_error { -public: - t_api_error(std::string msg, status_type http_code = status_type::bad_request) - : api_error(Type, std::move(msg), http_code) - {} + // Factory functions for some common types of DynamoDB API errors + static api_error validation(std::string msg) { + return api_error("ValidationException", std::move(msg)); + } + static api_error resource_not_found(std::string msg) { + return api_error("ResourceNotFoundException", std::move(msg)); + } + static api_error resource_in_use(std::string msg) { + return api_error("ResourceInUseException", std::move(msg)); + } + static api_error invalid_signature(std::string msg) { + return api_error("InvalidSignatureException", std::move(msg)); + } + static api_error unrecognized_client(std::string msg) { + return api_error("UnrecognizedClientException", std::move(msg)); + } + static api_error unknown_operation(std::string msg) { + return api_error("UnknownOperationException", std::move(msg)); + } + static api_error access_denied(std::string msg) { + return api_error("AccessDeniedException", std::move(msg)); + } + static api_error conditional_check_failed(std::string msg) { + return api_error("ConditionalCheckFailedException", std::move(msg)); + } + static api_error internal(std::string msg) { + return api_error("InternalServerError", std::move(msg), reply::status_type::internal_server_error); + } }; -} - -using validation_exception = detail::t_api_error; -using resource_not_found_exception = detail::t_api_error; -using access_denied_exception = detail::t_api_error; -using invalid_signature_exception = detail::t_api_error; } diff --git a/alternator/executor.cc b/alternator/executor.cc index 9e0ab1d863..c3674ee8e5 100644 --- a/alternator/executor.cc +++ b/alternator/executor.cc @@ -107,12 +107,12 @@ static constexpr int max_table_name_length = 222; // validate_table_name throws the appropriate api_error if this validation fails. static void validate_table_name(const std::string& name) { if (name.length() < 3 || name.length() > max_table_name_length) { - throw api_error("ValidationException", + throw api_error::validation( format("TableName must be at least 3 characters long and at most {} characters long", max_table_name_length)); } static const std::regex valid_table_name_chars ("[a-zA-Z0-9_.-]*"); if (!std::regex_match(name.c_str(), valid_table_name_chars)) { - throw api_error("ValidationException", + throw api_error::validation( "TableName must satisfy regular expression pattern: [a-zA-Z0-9_.-]+"); } } @@ -129,15 +129,15 @@ static void validate_table_name(const std::string& name) { static std::string view_name(const std::string& table_name, const std::string& index_name, const std::string& delim = ":") { static const std::regex valid_index_name_chars ("[a-zA-Z0-9_.-]*"); if (index_name.length() < 3) { - throw api_error("ValidationException", "IndexName must be at least 3 characters long"); + throw api_error::validation("IndexName must be at least 3 characters long"); } if (!std::regex_match(index_name.c_str(), valid_index_name_chars)) { - throw api_error("ValidationException", + throw api_error::validation( format("IndexName '{}' must satisfy regular expression pattern: [a-zA-Z0-9_.-]+", index_name)); } std::string ret = table_name + delim + index_name; if (ret.length() > max_table_name_length) { - throw api_error("ValidationException", + throw api_error::validation( format("The total length of TableName ('{}') and IndexName ('{}') cannot exceed {} characters", table_name, index_name, max_table_name_length - delim.size())); } @@ -160,7 +160,7 @@ static std::optional find_table_name(const rjson::value& request) { return std::nullopt; } if (!table_name_value->IsString()) { - throw validation_exception("Non-string TableName field in request"); + throw api_error::validation("Non-string TableName field in request"); } std::string table_name = table_name_value->GetString(); validate_table_name(table_name); @@ -170,7 +170,7 @@ static std::optional find_table_name(const rjson::value& request) { static std::string get_table_name(const rjson::value& request) { auto name = find_table_name(request); if (!name) { - throw validation_exception("Missing TableName field in request"); + throw api_error::validation("Missing TableName field in request"); } return *name; } @@ -190,7 +190,7 @@ schema_ptr executor::find_table(service::storage_proxy& proxy, const rjson::valu try { return proxy.get_db().local().find_schema(sstring(executor::KEYSPACE_NAME_PREFIX) + sstring(*table_name), *table_name); } catch(no_such_column_family&) { - throw api_error("ResourceNotFoundException", + throw api_error::resource_not_found( format("Requested resource not found: Table: {} not found", *table_name)); } } @@ -239,7 +239,7 @@ get_table_or_view(service::storage_proxy& proxy, const rjson::value& request) { try { return { proxy.get_db().local().find_schema(sstring(internal_ks_name), sstring(internal_table_name)), type }; } catch (no_such_column_family&) { - throw api_error("ResourceNotFoundException", + throw api_error::resource_not_found( format("Requested resource not found: Internal table: {}.{} not found", internal_ks_name, internal_table_name)); } } @@ -253,7 +253,7 @@ get_table_or_view(service::storage_proxy& proxy, const rjson::value& request) { table_name = view_name(orig_table_name, index_name->GetString()); type = table_or_view_type::gsi; } else { - throw api_error("ValidationException", + throw api_error::validation( format("Non-string IndexName '{}'", index_name->GetString())); } // If no tables for global indexes were found, the index may be local @@ -271,14 +271,14 @@ get_table_or_view(service::storage_proxy& proxy, const rjson::value& request) { // base table doesn't exist (ResourceNotFoundException) or it // does exist but the index does not (ValidationException). if (proxy.get_db().local().has_schema(keyspace_name, orig_table_name)) { - throw api_error("ValidationException", + throw api_error::validation( format("Requested resource not found: Index '{}' for table '{}'", index_name->GetString(), orig_table_name)); } else { - throw api_error("ResourceNotFoundException", + throw api_error::resource_not_found( format("Requested resource not found: Table: {} not found", orig_table_name)); } } else { - throw api_error("ResourceNotFoundException", + throw api_error::resource_not_found( format("Requested resource not found: Table: {} not found", table_name)); } } @@ -292,7 +292,7 @@ static std::string get_string_attribute(const rjson::value& value, std::string_v if (!attribute_value) return default_return; if (!attribute_value->IsString()) { - throw api_error("ValidationException", format("Expected string value for attribute {}, got: {}", + throw api_error::validation(format("Expected string value for attribute {}, got: {}", attribute_name, value)); } return std::string(attribute_value->GetString(), attribute_value->GetStringLength()); @@ -307,7 +307,7 @@ static bool get_bool_attribute(const rjson::value& value, std::string_view attri return default_return; } if (!attribute_value->IsBool()) { - throw api_error("ValidationException", format("Expected boolean value for attribute {}, got: {}", + throw api_error::validation(format("Expected boolean value for attribute {}, got: {}", attribute_name, value)); } return attribute_value->GetBool(); @@ -321,7 +321,7 @@ static std::optional get_int_attribute(const rjson::value& value, std::stri if (!attribute_value) return {}; if (!attribute_value->IsInt()) { - throw api_error("ValidationException", format("Expected integer value for attribute {}, got: {}", + throw api_error::validation(format("Expected integer value for attribute {}, got: {}", attribute_name, value)); } return attribute_value->GetInt(); @@ -468,7 +468,7 @@ future executor::delete_table(client_state& clien tracing::add_table_name(trace_state, keyspace_name, table_name); if (!_proxy.get_db().local().has_schema(keyspace_name, table_name)) { - return make_ready_future(api_error("ResourceNotFoundException", + return make_ready_future(api_error::resource_not_found( format("Requested resource not found: Table: {} not found", table_name))); } return _mm.announce_column_family_drop(keyspace_name, table_name, false, service::migration_manager::drop_views::yes).then([this, keyspace_name] { @@ -495,7 +495,7 @@ static data_type parse_key_type(const std::string& type) { case 'N': return decimal_type; // FIXME: use a specialized Alternator type, not the general "decimal_type". } } - throw api_error("ValidationException", + throw api_error::validation( format("Invalid key type '{}', can only be S, B or N.", type)); } @@ -506,7 +506,7 @@ static void add_column(schema_builder& builder, const std::string& name, const r // second column with the same name. We should fix this, by renaming // some column names which we want to reserve. if (name == executor::ATTRS_COLUMN_NAME) { - throw api_error("ValidationException", format("Column name '{}' is currently reserved. FIXME.", name)); + throw api_error::validation(format("Column name '{}' is currently reserved. FIXME.", name)); } for (auto it = attribute_definitions.Begin(); it != attribute_definitions.End(); ++it) { const rjson::value& attribute_info = *it; @@ -516,7 +516,7 @@ static void add_column(schema_builder& builder, const std::string& name, const r return; } } - throw api_error("ValidationException", + throw api_error::validation( format("KeySchema key '{}' missing in AttributeDefinitions", name)); } @@ -528,35 +528,35 @@ static void add_column(schema_builder& builder, const std::string& name, const r static std::pair parse_key_schema(const rjson::value& obj) { const rjson::value *key_schema; if (!obj.IsObject() || !(key_schema = rjson::find(obj, "KeySchema"))) { - throw api_error("ValidationException", "Missing KeySchema member"); + throw api_error::validation("Missing KeySchema member"); } if (!key_schema->IsArray() || key_schema->Size() < 1 || key_schema->Size() > 2) { - throw api_error("ValidationException", "KeySchema must list exactly one or two key columns"); + throw api_error::validation("KeySchema must list exactly one or two key columns"); } if (!(*key_schema)[0].IsObject()) { - throw api_error("ValidationException", "First element of KeySchema must be an object"); + throw api_error::validation("First element of KeySchema must be an object"); } const rjson::value *v = rjson::find((*key_schema)[0], "KeyType"); if (!v || !v->IsString() || v->GetString() != std::string("HASH")) { - throw api_error("ValidationException", "First key in KeySchema must be a HASH key"); + throw api_error::validation("First key in KeySchema must be a HASH key"); } v = rjson::find((*key_schema)[0], "AttributeName"); if (!v || !v->IsString()) { - throw api_error("ValidationException", "First key in KeySchema must have string AttributeName"); + throw api_error::validation("First key in KeySchema must have string AttributeName"); } std::string hash_key = v->GetString(); std::string range_key; if (key_schema->Size() == 2) { if (!(*key_schema)[1].IsObject()) { - throw api_error("ValidationException", "Second element of KeySchema must be an object"); + throw api_error::validation("Second element of KeySchema must be an object"); } v = rjson::find((*key_schema)[1], "KeyType"); if (!v || !v->IsString() || v->GetString() != std::string("RANGE")) { - throw api_error("ValidationException", "Second key in KeySchema must be a RANGE key"); + throw api_error::validation("Second key in KeySchema must be a RANGE key"); } v = rjson::find((*key_schema)[1], "AttributeName"); if (!v || !v->IsString()) { - throw api_error("ValidationException", "Second key in KeySchema must have string AttributeName"); + throw api_error::validation("Second key in KeySchema must have string AttributeName"); } range_key = v->GetString(); } @@ -584,16 +584,16 @@ static schema_ptr get_table_from_arn(service::storage_proxy& proxy, std::string_ // FIXME: remove sstring creation once find_schema gains a view-based interface return proxy.get_db().local().find_schema(sstring(keyspace_name), sstring(table_name)); } catch (const no_such_column_family& e) { - throw api_error("AccessDeniedException", "Incorrect resource identifier"); + throw api_error::access_denied("Incorrect resource identifier"); } catch (const std::out_of_range& e) { - throw api_error("AccessDeniedException", "Incorrect resource identifier"); + throw api_error::access_denied("Incorrect resource identifier"); } } const std::map& get_tags_of_table(schema_ptr schema) { auto it = schema->extensions().find(tags_extension::NAME); if (it == schema->extensions().end()) { - throw api_error("ValidationException", format("Table {} does not have valid tagging information", schema->ks_name())); + throw api_error::validation(format("Table {} does not have valid tagging information", schema->ks_name())); } auto tags_extension = static_pointer_cast(it->second); return tags_extension->tags(); @@ -622,7 +622,7 @@ static void validate_tags(const std::map& tags) { if (it != tags.end()) { std::string_view value = it->second; if (allowed_write_isolation_values.count(value) == 0) { - throw api_error("ValidationException", + throw api_error::validation( format("Incorrect write isolation tag {}. Allowed values: {}", value, allowed_write_isolation_values)); } } @@ -673,11 +673,11 @@ static void update_tags_map(const rjson::value& tags, std::map const rjson::value& value = (*it)["Value"]; auto tag_key = rjson::to_string_view(key); if (tag_key.empty() || tag_key.size() > 128 || !validate_legal_tag_chars(tag_key)) { - throw api_error("ValidationException", "The Tag Key provided is invalid string"); + throw api_error::validation("The Tag Key provided is invalid string"); } auto tag_value = rjson::to_string_view(value); if (tag_value.empty() || tag_value.size() > 256 || !validate_legal_tag_chars(tag_value)) { - throw api_error("ValidationException", "The Tag Value provided is invalid string"); + throw api_error::validation("The Tag Value provided is invalid string"); } tags_map[sstring(tag_key)] = sstring(tag_value); } @@ -688,7 +688,7 @@ static void update_tags_map(const rjson::value& tags, std::map } if (tags_map.size() > 50) { - throw api_error("ValidationException", "Number of Tags exceed the current limit for the provided ResourceArn"); + throw api_error::validation("Number of Tags exceed the current limit for the provided ResourceArn"); } validate_tags(tags_map); } @@ -708,16 +708,16 @@ future executor::tag_resource(client_state& clien return seastar::async([this, &client_state, request = std::move(request)] () mutable -> request_return_type { const rjson::value* arn = rjson::find(request, "ResourceArn"); if (!arn || !arn->IsString()) { - return api_error("AccessDeniedException", "Incorrect resource identifier"); + return api_error::access_denied("Incorrect resource identifier"); } schema_ptr schema = get_table_from_arn(_proxy, rjson::to_string_view(*arn)); std::map tags_map = get_tags_of_table(schema); const rjson::value* tags = rjson::find(request, "Tags"); if (!tags || !tags->IsArray()) { - return api_error("ValidationException", format("Cannot parse tags")); + return api_error::validation("Cannot parse tags"); } if (tags->Size() < 1) { - return api_error("ValidationException", "The number of tags must be at least 1") ; + return api_error::validation("The number of tags must be at least 1") ; } update_tags_map(*tags, tags_map, update_tags_action::add_tags); update_tags(_mm, schema, std::move(tags_map)).get(); @@ -731,11 +731,11 @@ future executor::untag_resource(client_state& cli return seastar::async([this, &client_state, request = std::move(request)] () -> request_return_type { const rjson::value* arn = rjson::find(request, "ResourceArn"); if (!arn || !arn->IsString()) { - return api_error("AccessDeniedException", "Incorrect resource identifier"); + return api_error::access_denied("Incorrect resource identifier"); } const rjson::value* tags = rjson::find(request, "TagKeys"); if (!tags || !tags->IsArray()) { - return api_error("ValidationException", format("Cannot parse tag keys")); + return api_error::validation(format("Cannot parse tag keys")); } schema_ptr schema = get_table_from_arn(_proxy, rjson::to_string_view(*arn)); @@ -751,7 +751,7 @@ future executor::list_tags_of_resource(client_sta _stats.api_operations.list_tags_of_resource++; const rjson::value* arn = rjson::find(request, "ResourceArn"); if (!arn || !arn->IsString()) { - return make_ready_future(api_error("AccessDeniedException", "Incorrect resource identifier")); + return make_ready_future(api_error::access_denied("Incorrect resource identifier")); } schema_ptr schema = get_table_from_arn(_proxy, rjson::to_string_view(*arn)); @@ -786,7 +786,7 @@ future executor::create_table(client_state& clien elogger.trace("Creating table {}", request); std::string table_name = get_table_name(request); if (table_name.find(INTERNAL_TABLE_PREFIX) == 0) { - return make_ready_future(api_error("ValidationException", + return make_ready_future(api_error::validation( format("Prefix {} is reserved for accessing internal tables", INTERNAL_TABLE_PREFIX))); } std::string keyspace_name = executor::KEYSPACE_NAME_PREFIX + table_name; @@ -807,16 +807,16 @@ future executor::create_table(client_state& clien std::string billing_mode = get_string_attribute(request, "BillingMode", "PROVISIONED"); if (billing_mode == "PAY_PER_REQUEST") { if (rjson::find(request, "ProvisionedThroughput")) { - return make_ready_future(api_error("ValidationException", + return make_ready_future(api_error::validation( "When BillingMode=PAY_PER_REQUEST, ProvisionedThroughput cannot be specified.")); } } else if (billing_mode == "PROVISIONED") { if (!rjson::find(request, "ProvisionedThroughput")) { - return make_ready_future(api_error("ValidationException", + return make_ready_future(api_error::validation( "When BillingMode=PROVISIONED, ProvisionedThroughput must be specified.")); } } else { - return make_ready_future(api_error("ValidationException", + return make_ready_future(api_error::validation( "Unknown BillingMode={}. Must be PAY_PER_REQUEST or PROVISIONED.")); } @@ -830,12 +830,12 @@ future executor::create_table(client_state& clien std::vector where_clauses; if (gsi) { if (!gsi->IsArray()) { - return make_ready_future(api_error("ValidationException", "GlobalSecondaryIndexes must be an array.")); + return make_ready_future(api_error::validation("GlobalSecondaryIndexes must be an array.")); } for (const rjson::value& g : gsi->GetArray()) { const rjson::value* index_name = rjson::find(g, "IndexName"); if (!index_name || !index_name->IsString()) { - return make_ready_future(api_error("ValidationException", "GlobalSecondaryIndexes IndexName must be a string.")); + return make_ready_future(api_error::validation("GlobalSecondaryIndexes IndexName must be a string.")); } std::string vname(view_name(table_name, index_name->GetString())); elogger.trace("Adding GSI {}", index_name->GetString()); @@ -883,12 +883,12 @@ future executor::create_table(client_state& clien const rjson::value* lsi = rjson::find(request, "LocalSecondaryIndexes"); if (lsi) { if (!lsi->IsArray()) { - throw api_error("ValidationException", "LocalSecondaryIndexes must be an array."); + throw api_error::validation("LocalSecondaryIndexes must be an array."); } for (const rjson::value& l : lsi->GetArray()) { const rjson::value* index_name = rjson::find(l, "IndexName"); if (!index_name || !index_name->IsString()) { - throw api_error("ValidationException", "LocalSecondaryIndexes IndexName must be a string."); + throw api_error::validation("LocalSecondaryIndexes IndexName must be a string."); } std::string vname(lsi_name(table_name, index_name->GetString())); elogger.trace("Adding LSI {}", index_name->GetString()); @@ -897,15 +897,15 @@ future executor::create_table(client_state& clien schema_builder view_builder(keyspace_name, vname); auto [view_hash_key, view_range_key] = parse_key_schema(l); if (view_hash_key != hash_key) { - return make_ready_future(api_error("ValidationException", + return make_ready_future(api_error::validation( "LocalSecondaryIndex hash key must match the base table hash key")); } add_column(view_builder, view_hash_key, attribute_definitions, column_kind::partition_key); if (view_range_key.empty()) { - return make_ready_future(api_error("ValidationException", "LocalSecondaryIndex must specify a sort key")); + return make_ready_future(api_error::validation("LocalSecondaryIndex must specify a sort key")); } if (view_range_key == hash_key) { - return make_ready_future(api_error("ValidationException", "LocalSecondaryIndex sort key cannot be the same as hash key")); + return make_ready_future(api_error::validation("LocalSecondaryIndex sort key cannot be the same as hash key")); } if (view_range_key != range_key) { add_column(builder, view_range_key, attribute_definitions, column_kind::regular_column); @@ -930,7 +930,7 @@ future executor::create_table(client_state& clien } } if (rjson::find(request, "SSESpecification")) { - return make_ready_future(api_error("ValidationException", "SSESpecification: configuring encryption-at-rest is not yet supported.")); + return make_ready_future(api_error::validation("SSESpecification: configuring encryption-at-rest is not yet supported.")); } rjson::value* stream_specification = rjson::find(request, "StreamSpecification"); @@ -986,8 +986,7 @@ future executor::create_table(client_state& clien }); }).handle_exception_type([table_name = std::move(table_name)] (exceptions::already_exists_exception&) { return make_exception_future( - api_error("ResourceInUseException", - format("Table {} already exists", table_name))); + api_error::resource_in_use(format("Table {} already exists", table_name))); }); }); } @@ -998,7 +997,7 @@ future executor::update_table(client_state& clien std::string table_name = get_table_name(request); if (table_name.find(INTERNAL_TABLE_PREFIX) == 0) { - return make_ready_future(validation_exception( + return make_ready_future(api_error::validation( format("Prefix {} is reserved for accessing internal tables", INTERNAL_TABLE_PREFIX))); } std::string keyspace_name = executor::KEYSPACE_NAME_PREFIX + table_name; @@ -1024,7 +1023,7 @@ future executor::update_table(client_state& clien for (auto& s : unsupported) { if (rjson::find(request, s)) { - throw validation_exception(s + " not supported"); + throw api_error::validation(s + " not supported"); } } @@ -1075,7 +1074,7 @@ public: // check that the key doesn't have any spurious components. static void check_key(const rjson::value& key, const schema_ptr& schema) { if (key.MemberCount() != (schema->clustering_key_size() == 0 ? 1 : 2)) { - throw api_error("ValidationException", "Given key attribute not in schema"); + throw api_error::validation("Given key attribute not in schema"); } } @@ -1084,24 +1083,24 @@ static void check_key(const rjson::value& key, const schema_ptr& schema) { // (somewhat artificially) forbidden by DynamoDB. void validate_value(const rjson::value& v, const char* caller) { if (!v.IsObject() || v.MemberCount() != 1) { - throw api_error("ValidationException", format("{}: improperly formatted value '{}'", caller, v)); + throw api_error::validation(format("{}: improperly formatted value '{}'", caller, v)); } auto it = v.MemberBegin(); const std::string_view type = rjson::to_string_view(it->name); if (type == "SS" || type == "BS" || type == "NS") { if (!it->value.IsArray()) { - throw api_error("ValidationException", format("{}: improperly formatted set '{}'", caller, v)); + throw api_error::validation(format("{}: improperly formatted set '{}'", caller, v)); } if (it->value.Size() == 0) { - throw api_error("ValidationException", format("{}: empty set not allowed", caller)); + throw api_error::validation(format("{}: empty set not allowed", caller)); } } else if (type == "S" || type == "B") { if (!it->value.IsString()) { - throw api_error("ValidationException", format("{}: improperly formatted value '{}'", caller, v)); + throw api_error::validation(format("{}: improperly formatted value '{}'", caller, v)); } } else if (type != "N" && type != "L" && type != "M" && type != "BOOL" && type != "NULL") { // TODO: can do more sanity checks on the content of the above types. - throw api_error("ValidationException", format("{}: unknown type {} for value {}", caller, type, v)); + throw api_error::validation(format("{}: unknown type {} for value {}", caller, type, v)); } } @@ -1260,7 +1259,7 @@ rmw_operation::returnvalues rmw_operation::parse_returnvalues(const rjson::value return rmw_operation::returnvalues::NONE; } if (!attribute_value->IsString()) { - throw api_error("ValidationException", format("Expected string value for ReturnValues, got: {}", *attribute_value)); + throw api_error::validation(format("Expected string value for ReturnValues, got: {}", *attribute_value)); } auto s = rjson::to_string_view(*attribute_value); if (s == "NONE") { @@ -1274,7 +1273,7 @@ rmw_operation::returnvalues rmw_operation::parse_returnvalues(const rjson::value } else if (s == "UPDATED_NEW") { return rmw_operation::returnvalues::UPDATED_NEW; } else { - throw api_error("ValidationException", format("Unrecognized value for ReturnValues: {}", s)); + throw api_error::validation(format("Unrecognized value for ReturnValues: {}", s)); } } @@ -1377,7 +1376,7 @@ future rmw_operation::execute(service::storage_pr stats& stats) { if (needs_read_before_write) { if (_write_isolation == write_isolation::FORBID_RMW) { - throw api_error("ValidationException", "Read-modify-write operations are disabled by 'forbid_rmw' write isolation policy. Refer to https://github.com/scylladb/scylla/blob/master/docs/alternator/alternator.md#write-isolation-policies for more information."); + throw api_error::validation("Read-modify-write operations are disabled by 'forbid_rmw' write isolation policy. Refer to https://github.com/scylladb/scylla/blob/master/docs/alternator/alternator.md#write-isolation-policies for more information."); } stats.reads_before_write++; if (_write_isolation == write_isolation::UNSAFE_RMW) { @@ -1387,7 +1386,7 @@ future rmw_operation::execute(service::storage_pr [this, &client_state, &proxy, trace_state, permit = std::move(permit)] (std::unique_ptr previous_item) mutable { std::optional m = apply(std::move(previous_item), api::new_timestamp()); if (!m) { - return make_ready_future(api_error("ConditionalCheckFailedException", "Failed condition.")); + return make_ready_future(api_error::conditional_check_failed("Failed condition.")); } return proxy.mutate(std::vector{std::move(*m)}, db::consistency_level::LOCAL_QUORUM, executor::default_timeout(), trace_state, std::move(permit)).then([this] () mutable { return rmw_operation_return(std::move(_return_attributes)); @@ -1412,7 +1411,7 @@ future rmw_operation::execute(service::storage_pr {timeout, std::move(permit), client_state, trace_state}, db::consistency_level::LOCAL_SERIAL, db::consistency_level::LOCAL_QUORUM, timeout, timeout).then([this, read_command] (bool is_applied) mutable { if (!is_applied) { - return make_ready_future(api_error("ConditionalCheckFailedException", "Failed condition.")); + return make_ready_future(api_error::conditional_check_failed("Failed condition.")); } return rmw_operation_return(std::move(_return_attributes)); }); @@ -1425,15 +1424,15 @@ static parsed::condition_expression get_parsed_condition_expression(rjson::value return parsed::condition_expression{}; } if (!condition_expression->IsString()) { - throw api_error("ValidationException", "ConditionExpression must be a string"); + throw api_error::validation("ConditionExpression must be a string"); } if (condition_expression->GetStringLength() == 0) { - throw api_error("ValidationException", "ConditionExpression must not be empty"); + throw api_error::validation("ConditionExpression must not be empty"); } try { return parse_condition_expression(condition_expression->GetString()); } catch(expressions_syntax_error& e) { - throw api_error("ValidationException", e.what()); + throw api_error::validation(e.what()); } } @@ -1454,7 +1453,7 @@ static void verify_all_are_used(const rjson::value& req, const char* field, } for (auto it = attribute_names->MemberBegin(); it != attribute_names->MemberEnd(); ++it) { if (!used.count(it->name.GetString())) { - throw api_error("ValidationException", + throw api_error::validation( format("{} has spurious '{}', not used in {}", field, it->name.GetString(), operation)); } @@ -1472,7 +1471,7 @@ public: _pk = _mutation_builder.pk(); _ck = _mutation_builder.ck(); if (_returnvalues != returnvalues::NONE && _returnvalues != returnvalues::ALL_OLD) { - throw api_error("ValidationException", format("PutItem supports only NONE or ALL_OLD for ReturnValues")); + throw api_error::validation(format("PutItem supports only NONE or ALL_OLD for ReturnValues")); } _condition_expression = get_parsed_condition_expression(_request); const rjson::value* expression_attribute_names = rjson::find(_request, "ExpressionAttributeNames"); @@ -1487,10 +1486,10 @@ public: verify_all_are_used(_request, "ExpressionAttributeValues", used_attribute_values, "PutItem"); } else { if (expression_attribute_names) { - throw api_error("ValidationException", "ExpressionAttributeNames cannot be used without ConditionExpression"); + throw api_error::validation("ExpressionAttributeNames cannot be used without ConditionExpression"); } if (expression_attribute_values) { - throw api_error("ValidationException", "ExpressionAttributeValues cannot be used without ConditionExpression"); + throw api_error::validation("ExpressionAttributeValues cannot be used without ConditionExpression"); } } } @@ -1555,7 +1554,7 @@ public: _pk = _mutation_builder.pk(); _ck = _mutation_builder.ck(); if (_returnvalues != returnvalues::NONE && _returnvalues != returnvalues::ALL_OLD) { - throw api_error("ValidationException", format("DeleteItem supports only NONE or ALL_OLD for ReturnValues")); + throw api_error::validation(format("DeleteItem supports only NONE or ALL_OLD for ReturnValues")); } _condition_expression = get_parsed_condition_expression(_request); const rjson::value* expression_attribute_names = rjson::find(_request, "ExpressionAttributeNames"); @@ -1570,10 +1569,10 @@ public: verify_all_are_used(_request, "ExpressionAttributeValues", used_attribute_values, "DeleteItem"); } else { if (expression_attribute_names) { - throw api_error("ValidationException", "ExpressionAttributeNames cannot be used without ConditionExpression"); + throw api_error::validation("ExpressionAttributeNames cannot be used without ConditionExpression"); } if (expression_attribute_values) { - throw api_error("ValidationException", "ExpressionAttributeValues cannot be used without ConditionExpression"); + throw api_error::validation("ExpressionAttributeValues cannot be used without ConditionExpression"); } } } @@ -1633,7 +1632,7 @@ static schema_ptr get_table_from_batch_request(const service::storage_proxy& pro try { return proxy.get_db().local().find_schema(sstring(executor::KEYSPACE_NAME_PREFIX) + table_name, table_name); } catch(no_such_column_family&) { - throw api_error("ResourceNotFoundException", format("Requested resource not found: Table: {} not found", table_name)); + throw api_error::resource_not_found(format("Requested resource not found: Table: {} not found", table_name)); } } @@ -1801,7 +1800,7 @@ future executor::batch_write_item(client_state& c 1, primary_key_hash{schema}, primary_key_equal{schema}); for (auto& request : it->value.GetArray()) { if (!request.IsObject() || request.MemberCount() != 1) { - return make_ready_future(api_error("ValidationException", format("Invalid BatchWriteItem request: {}", request))); + return make_ready_future(api_error::validation(format("Invalid BatchWriteItem request: {}", request))); } auto r = request.MemberBegin(); const std::string r_name = r->name.GetString(); @@ -1812,7 +1811,7 @@ future executor::batch_write_item(client_state& c item, schema, put_or_delete_item::put_item{})); auto mut_key = std::make_pair(mutation_builders.back().second.pk(), mutation_builders.back().second.ck()); if (used_keys.count(mut_key) > 0) { - return make_ready_future(api_error("ValidationException", "Provided list of item keys contains duplicates")); + return make_ready_future(api_error::validation("Provided list of item keys contains duplicates")); } used_keys.insert(std::move(mut_key)); } else if (r_name == "DeleteRequest") { @@ -1822,11 +1821,11 @@ future executor::batch_write_item(client_state& c auto mut_key = std::make_pair(mutation_builders.back().second.pk(), mutation_builders.back().second.ck()); if (used_keys.count(mut_key) > 0) { - return make_ready_future(api_error("ValidationException", "Provided list of item keys contains duplicates")); + return make_ready_future(api_error::validation("Provided list of item keys contains duplicates")); } used_keys.insert(std::move(mut_key)); } else { - return make_ready_future(api_error("ValidationException", format("Unknown BatchWriteItem request type: {}", r_name))); + return make_ready_future(api_error::validation(format("Unknown BatchWriteItem request type: {}", r_name))); } } } @@ -1843,7 +1842,7 @@ future executor::batch_write_item(client_state& c static std::string get_item_type_string(const rjson::value& v) { if (!v.IsObject() || v.MemberCount() != 1) { - throw api_error("ValidationException", format("Item has invalid format: {}", v)); + throw api_error::validation(format("Item has invalid format: {}", v)); } auto it = v.MemberBegin(); return it->name.GetString(); @@ -1863,7 +1862,7 @@ std::unordered_set calculate_attrs_to_get(const rjson::value& req, const bool has_attributes_to_get = req.HasMember("AttributesToGet"); const bool has_projection_expression = req.HasMember("ProjectionExpression"); if (has_attributes_to_get && has_projection_expression) { - throw api_error("ValidationException", + throw api_error::validation( format("GetItem does not allow both ProjectionExpression and AttributesToGet to be given together")); } if (has_attributes_to_get) { @@ -1880,7 +1879,7 @@ std::unordered_set calculate_attrs_to_get(const rjson::value& req, try { paths_to_get = parse_projection_expression(projection_expression.GetString()); } catch(expressions_syntax_error& e) { - throw api_error("ValidationException", e.what()); + throw api_error::validation(e.what()); } resolve_projection_expression(paths_to_get, expression_attribute_names, used_attribute_names); std::unordered_set seen_column_names; @@ -1888,11 +1887,11 @@ std::unordered_set calculate_attrs_to_get(const rjson::value& req, boost::adaptors::transformed([&] (const parsed::path& p) { if (p.has_operators()) { // FIXME: this check will need to change when we support non-toplevel attributes - throw api_error("ValidationException", "Non-toplevel attributes in ProjectionExpression not yet implemented"); + throw api_error::validation("Non-toplevel attributes in ProjectionExpression not yet implemented"); } if (!seen_column_names.insert(p.root()).second) { // FIXME: this check will need to change when we support non-toplevel attributes - throw api_error("ValidationException", + throw api_error::validation( format("Invalid ProjectionExpression: two document paths overlap with each other: {} and {}.", p.root(), p.root())); } @@ -2028,7 +2027,7 @@ update_item_operation::update_item_operation(service::storage_proxy& proxy, rjso { const rjson::value* key = rjson::find(_request, "Key"); if (!key) { - throw api_error("ValidationException", "UpdateItem requires a Key parameter"); + throw api_error::validation("UpdateItem requires a Key parameter"); } _pk = pk_from_json(*key, _schema); _ck = ck_from_json(*key, _schema); @@ -2042,7 +2041,7 @@ update_item_operation::update_item_operation(service::storage_proxy& proxy, rjso const rjson::value* update_expression = rjson::find(_request, "UpdateExpression"); if (update_expression) { if (!update_expression->IsString()) { - throw api_error("ValidationException", "UpdateExpression must be a string"); + throw api_error::validation("UpdateExpression must be a string"); } try { _update_expression = parse_update_expression(update_expression->GetString()); @@ -2050,16 +2049,16 @@ update_item_operation::update_item_operation(service::storage_proxy& proxy, rjso expression_attribute_names, expression_attribute_values, used_attribute_names, used_attribute_values); } catch(expressions_syntax_error& e) { - throw api_error("ValidationException", e.what()); + throw api_error::validation(e.what()); } if (_update_expression.empty()) { - throw api_error("ValidationException", "Empty expression in UpdateExpression is not allowed"); + throw api_error::validation("Empty expression in UpdateExpression is not allowed"); } } _attribute_updates = rjson::find(_request, "AttributeUpdates"); if (_attribute_updates) { if (!_attribute_updates->IsObject()) { - throw api_error("ValidationException", "AttributeUpdates must be an object"); + throw api_error::validation("AttributeUpdates must be an object"); } } @@ -2075,15 +2074,15 @@ update_item_operation::update_item_operation(service::storage_proxy& proxy, rjso // and new-style UpdateExpression or ConditionExpression in the same request const rjson::value* expected = rjson::find(_request, "Expected"); if (update_expression && _attribute_updates) { - throw api_error("ValidationException", + throw api_error::validation( format("UpdateItem does not allow both AttributeUpdates and UpdateExpression to be given together")); } if (update_expression && expected) { - throw api_error("ValidationException", + throw api_error::validation( format("UpdateItem does not allow both old-style Expected and new-style UpdateExpression to be given together")); } if (_attribute_updates && !_condition_expression.empty()) { - throw api_error("ValidationException", + throw api_error::validation( format("UpdateItem does not allow both old-style AttributeUpdates and new-style ConditionExpression to be given together")); } } @@ -2179,12 +2178,12 @@ update_item_operation::apply(std::unique_ptr previous_item, api::t for (auto& action : _update_expression.actions()) { if (action._path.has_operators()) { // FIXME: implement this case - throw api_error("ValidationException", "UpdateItem support for nested updates not yet implemented"); + throw api_error::validation("UpdateItem support for nested updates not yet implemented"); } std::string column_name = action._path.root(); const column_definition* cdef = _schema->get_column_definition(to_bytes(column_name)); if (cdef && cdef->is_primary_key()) { - throw api_error("ValidationException", + throw api_error::validation( format("UpdateItem cannot update key column {}", column_name)); } // DynamoDB forbids multiple updates in the same expression to @@ -2193,7 +2192,7 @@ update_item_operation::apply(std::unique_ptr previous_item, api::t // FIXME: currently, without full support for document paths, // we only check if the paths' roots are the same. if (!seen_column_names.insert(column_name).second) { - throw api_error("ValidationException", + throw api_error::validation( format("Invalid UpdateExpression: two document paths overlap with each other: {} and {}.", column_name, column_name)); } @@ -2216,16 +2215,16 @@ update_item_operation::apply(std::unique_ptr previous_item, api::t std::string v1_type = get_item_type_string(v1); if (v1_type == "N") { if (get_item_type_string(v2) != "N") { - throw api_error("ValidationException", format("Incorrect operand type for operator or function. Expected {}: {}", v1_type, rjson::print(v2))); + throw api_error::validation(format("Incorrect operand type for operator or function. Expected {}: {}", v1_type, rjson::print(v2))); } result = number_add(v1, v2); } else if (v1_type == "SS" || v1_type == "NS" || v1_type == "BS") { if (get_item_type_string(v2) != v1_type) { - throw api_error("ValidationException", format("Incorrect operand type for operator or function. Expected {}: {}", v1_type, rjson::print(v2))); + throw api_error::validation(format("Incorrect operand type for operator or function. Expected {}: {}", v1_type, rjson::print(v2))); } result = set_sum(v1, v2); } else { - throw api_error("ValidationException", format("An operand in the update expression has an incorrect data type: {}", v1)); + throw api_error::validation(format("An operand in the update expression has an incorrect data type: {}", v1)); } do_update(to_bytes(column_name), result); }, @@ -2257,7 +2256,7 @@ update_item_operation::apply(std::unique_ptr previous_item, api::t bytes column_name = to_bytes(it->name.GetString()); const column_definition* cdef = _schema->get_column_definition(column_name); if (cdef && cdef->is_primary_key()) { - throw api_error("ValidationException", + throw api_error::validation( format("UpdateItem cannot update key column {}", it->name.GetString())); } std::string action = (it->value)["Action"].GetString(); @@ -2267,7 +2266,7 @@ update_item_operation::apply(std::unique_ptr previous_item, api::t // we need to verify the old type and/or value is same as // specified before deleting... We don't do this yet. if (it->value.HasMember("Value")) { - throw api_error("ValidationException", + throw api_error::validation( format("UpdateItem DELETE with checking old value not yet supported")); } do_delete(std::move(column_name)); @@ -2277,7 +2276,7 @@ update_item_operation::apply(std::unique_ptr previous_item, api::t do_update(std::move(column_name), value); } else { // FIXME: need to support "ADD" as well. - throw api_error("ValidationException", + throw api_error::validation( format("Unknown Action value '{}' in AttributeUpdates", action)); } } @@ -2350,7 +2349,7 @@ static db::consistency_level get_read_consistency(const rjson::value& request) { if (consistent_read_value->IsBool()) { consistent_read = consistent_read_value->GetBool(); } else { - throw api_error("ValidationException", "ConsistentRead flag must be a boolean"); + throw api_error::validation("ConsistentRead flag must be a boolean"); } } return consistent_read ? db::consistency_level::LOCAL_QUORUM : db::consistency_level::LOCAL_ONE; @@ -2551,20 +2550,20 @@ filter::filter(const rjson::value& request, request_type rt, auto conditional_operator = get_conditional_operator(request); if (conditional_operator != conditional_operator_type::MISSING && (!conditions || (conditions->IsObject() && conditions->GetObject().ObjectEmpty()))) { - throw api_error("ValidationException", + throw api_error::validation( format("'ConditionalOperator' parameter cannot be specified for missing or empty {}", conditions_attribute)); } if (expression && conditions) { - throw api_error("ValidationException", + throw api_error::validation( format("FilterExpression and {} are not allowed together", conditions_attribute)); } if (expression) { if (!expression->IsString()) { - throw api_error("ValidationException", "FilterExpression must be a string"); + throw api_error::validation("FilterExpression must be a string"); } if (expression->GetStringLength() == 0) { - throw api_error("ValidationException", "FilterExpression must not be empty"); + throw api_error::validation("FilterExpression must not be empty"); } try { // FIXME: make parse_condition_expression take string_view, get @@ -2577,7 +2576,7 @@ filter::filter(const rjson::value& request, request_type rt, used_attribute_names, used_attribute_values); _imp = expression_filter { std::move(parsed) }; } catch(expressions_syntax_error& e) { - throw api_error("ValidationException", e.what()); + throw api_error::validation(e.what()); } } if (conditions) { @@ -2834,15 +2833,15 @@ future executor::scan(client_state& client_state, auto total_segments = get_int_attribute(request, "TotalSegments"); if (segment || total_segments) { if (!segment || !total_segments) { - return make_ready_future(api_error("ValidationException", + return make_ready_future(api_error::validation( "Both Segment and TotalSegments attributes need to be present for a parallel scan")); } if (*segment < 0 || *segment >= *total_segments) { - return make_ready_future(api_error("ValidationException", + return make_ready_future(api_error::validation( "Segment must be non-negative and less than TotalSegments")); } if (*total_segments < 0 || *total_segments > 1000000) { - return make_ready_future(api_error("ValidationException", + return make_ready_future(api_error::validation( "TotalSegments must be non-negative and less or equal to 1000000")); } } @@ -2851,13 +2850,13 @@ future executor::scan(client_state& client_state, db::consistency_level cl = get_read_consistency(request); if (table_type == table_or_view_type::gsi && cl != db::consistency_level::LOCAL_ONE) { - return make_ready_future(api_error("ValidationException", + return make_ready_future(api_error::validation( "Consistent reads are not allowed on global indexes (GSI)")); } rjson::value* limit_json = rjson::find(request, "Limit"); uint32_t limit = limit_json ? limit_json->GetUint64() : query::max_partitions; if (limit <= 0) { - return make_ready_future(api_error("ValidationException", "Limit must be greater than 0")); + return make_ready_future(api_error::validation("Limit must be greater than 0")); } std::unordered_set used_attribute_names; @@ -2889,10 +2888,10 @@ future executor::scan(client_state& client_state, static dht::partition_range calculate_pk_bound(schema_ptr schema, const column_definition& pk_cdef, const rjson::value& comp_definition, const rjson::value& attrs) { auto op = get_comparison_operator(comp_definition); if (op != comparison_operator_type::EQ) { - throw api_error("ValidationException", format("Hash key can only be restricted with equality operator (EQ). {} not supported.", comp_definition)); + throw api_error::validation(format("Hash key can only be restricted with equality operator (EQ). {} not supported.", comp_definition)); } if (attrs.Size() != 1) { - throw api_error("ValidationException", format("A single attribute is required for a hash key EQ restriction: {}", attrs)); + throw api_error::validation(format("A single attribute is required for a hash key EQ restriction: {}", attrs)); } bytes raw_value = get_key_from_typed_value(attrs[0], pk_cdef); partition_key pk = partition_key::from_singular(*schema, pk_cdef.type->deserialize(raw_value)); @@ -2915,7 +2914,7 @@ static query::clustering_range calculate_ck_bound(schema_ptr schema, const colum auto op = get_comparison_operator(comp_definition); const size_t expected_attrs_size = (op == comparison_operator_type::BETWEEN) ? 2 : 1; if (attrs.Size() != expected_attrs_size) { - throw api_error("ValidationException", format("{} arguments expected for a sort key restriction: {}", expected_attrs_size, attrs)); + throw api_error::validation(format("{} arguments expected for a sort key restriction: {}", expected_attrs_size, attrs)); } bytes raw_value = get_key_from_typed_value(attrs[0], ck_cdef); clustering_key ck = clustering_key::from_single_value(*schema, raw_value); @@ -2942,12 +2941,12 @@ static query::clustering_range calculate_ck_bound(schema_ptr schema, const colum // NOTICE(sarna): A range starting with given prefix and ending (non-inclusively) with a string "incremented" by a single // character at the end. Throws for NUMBER instances. if (!ck_cdef.type->is_compatible_with(*utf8_type)) { - throw api_error("ValidationException", format("BEGINS_WITH operator cannot be applied to type {}", type_to_string(ck_cdef.type))); + throw api_error::validation(format("BEGINS_WITH operator cannot be applied to type {}", type_to_string(ck_cdef.type))); } return get_clustering_range_for_begins_with(std::move(raw_value), ck, schema, ck_cdef.type); } default: - throw api_error("ValidationException", format("Operator {} not supported for sort key", comp_definition)); + throw api_error::validation(format("Operator {} not supported for sort key", comp_definition)); } } @@ -2968,13 +2967,13 @@ calculate_bounds_conditions(schema_ptr schema, const rjson::value& conditions) { const column_definition* ck_cdef = schema->clustering_key_size() > 0 ? &schema->clustering_key_columns().front() : nullptr; if (sstring(key) == pk_cdef.name_as_text()) { if (!partition_ranges.empty()) { - throw api_error("ValidationException", "Currently only a single restriction per key is allowed"); + throw api_error::validation("Currently only a single restriction per key is allowed"); } partition_ranges.push_back(calculate_pk_bound(schema, pk_cdef, comp_definition, attr_list)); } if (ck_cdef && sstring(key) == ck_cdef->name_as_text()) { if (!ck_bounds.empty()) { - throw api_error("ValidationException", "Currently only a single restriction per key is allowed"); + throw api_error::validation("Currently only a single restriction per key is allowed"); } ck_bounds.push_back(calculate_ck_bound(schema, *ck_cdef, comp_definition, attr_list)); } @@ -2983,17 +2982,17 @@ calculate_bounds_conditions(schema_ptr schema, const rjson::value& conditions) { // Validate that a query's conditions must be on the hash key, and // optionally also on the sort key if it exists. if (partition_ranges.empty()) { - throw api_error("ValidationException", format("Query missing condition on hash key '{}'", schema->partition_key_columns().front().name_as_text())); + throw api_error::validation(format("Query missing condition on hash key '{}'", schema->partition_key_columns().front().name_as_text())); } if (schema->clustering_key_size() == 0) { if (conditions.MemberCount() != 1) { - throw api_error("ValidationException", "Only one condition allowed in table with only hash key"); + throw api_error::validation("Only one condition allowed in table with only hash key"); } } else { if (conditions.MemberCount() == 2 && ck_bounds.empty()) { - throw api_error("ValidationException", format("Query missing condition on sort key '{}'", schema->clustering_key_columns().front().name_as_text())); + throw api_error::validation(format("Query missing condition on sort key '{}'", schema->clustering_key_columns().front().name_as_text())); } else if (conditions.MemberCount() > 2) { - throw api_error("ValidationException", "Only one or two conditions allowed in table with hash key and sort key"); + throw api_error::validation("Only one or two conditions allowed in table with hash key and sort key"); } } @@ -3016,19 +3015,19 @@ static std::string_view get_toplevel(const parsed::value& v, { const parsed::path& path = std::get(v._value); if (path.has_operators()) { - throw api_error("ValidationException", "KeyConditionExpression does not support nested attributes"); + throw api_error::validation("KeyConditionExpression does not support nested attributes"); } std::string_view column_name = path.root(); if (column_name.size() > 0 && column_name[0] == '#') { used_attribute_names.emplace(column_name); if (!expression_attribute_names) { - throw api_error("ValidationException", + throw api_error::validation( format("ExpressionAttributeNames missing, entry '{}' required by KeyConditionExpression", column_name)); } const rjson::value* value = rjson::find(*expression_attribute_names, column_name); if (!value || !value->IsString()) { - throw api_error("ValidationException", + throw api_error::validation( format("ExpressionAttributeNames missing entry '{}' required by KeyConditionExpression", column_name)); } @@ -3061,7 +3060,7 @@ static void condition_expression_and_list( std::vector& conditions) { if (condition_expression._negated) { - throw api_error("ValidationException", "KeyConditionExpression cannot use NOT"); + throw api_error::validation("KeyConditionExpression cannot use NOT"); } std::visit(overloaded_functor { [&] (const parsed::primitive_condition& cond) { @@ -3069,7 +3068,7 @@ static void condition_expression_and_list( }, [&] (const parsed::condition_expression::condition_list& list) { if (list.op == '|' && list.conditions.size() > 1) { - throw api_error("ValidationException", "KeyConditionExpression cannot use OR"); + throw api_error::validation("KeyConditionExpression cannot use OR"); } for (const parsed::condition_expression& cond : list.conditions) { condition_expression_and_list(cond, conditions); @@ -3088,10 +3087,10 @@ calculate_bounds_condition_expression(schema_ptr schema, std::unordered_set& used_attribute_names) { if (!expression.IsString()) { - throw api_error("ValidationException", "KeyConditionExpression must be a string"); + throw api_error::validation("KeyConditionExpression must be a string"); } if (expression.GetStringLength() == 0) { - throw api_error("ValidationException", "KeyConditionExpression must not be empty"); + throw api_error::validation("KeyConditionExpression must not be empty"); } // We parse the KeyConditionExpression with the same parser we use for // ConditionExpression. But KeyConditionExpression only supports a subset @@ -3103,7 +3102,7 @@ calculate_bounds_condition_expression(schema_ptr schema, try { p = parse_condition_expression(std::string(rjson::to_string_view(expression))); } catch(expressions_syntax_error& e) { - throw api_error("ValidationException", e.what()); + throw api_error::validation(e.what()); } resolve_condition_expression(p, expression_attribute_names, expression_attribute_values, @@ -3112,7 +3111,7 @@ calculate_bounds_condition_expression(schema_ptr schema, condition_expression_and_list(p, conditions); if (conditions.size() < 1 || conditions.size() > 2) { - throw api_error("ValidationException", + throw api_error::validation( "KeyConditionExpression syntax error: must have 1 or 2 conditions"); } // Scylla allows us to have an (equality) constraint on the partition key @@ -3143,15 +3142,15 @@ calculate_bounds_condition_expression(schema_ptr schema, // value reference.. const parsed::value::function_call *f = std::get_if(&cond._values[0]._value); if (!f) { - throw api_error("ValidationException", "KeyConditionExpression cannot be just a value"); + throw api_error::validation("KeyConditionExpression cannot be just a value"); } if (f->_function_name != "begins_with") { - throw api_error("ValidationException", + throw api_error::validation( format("KeyConditionExpression function '{}' not supported",f->_function_name)); } if (f->_parameters.size() != 2 || !f->_parameters[0].is_path() || !f->_parameters[1].is_constant()) { - throw api_error("ValidationException", + throw api_error::validation( "KeyConditionExpression begins_with() takes attribute and value"); } key = get_toplevel(f->_parameters[0], expression_attribute_names, used_attribute_names); @@ -3164,7 +3163,7 @@ calculate_bounds_condition_expression(schema_ptr schema, } else if (cond._values[1].is_path() && cond._values[0].is_constant()) { toplevel_ind = 1; } else { - throw api_error("ValidationException", "KeyConditionExpression must compare attribute with constant"); + throw api_error::validation("KeyConditionExpression must compare attribute with constant"); } key = get_toplevel(cond._values[toplevel_ind], expression_attribute_names, used_attribute_names); break; @@ -3179,7 +3178,7 @@ calculate_bounds_condition_expression(schema_ptr schema, toplevel_ind = 0; key = get_toplevel(cond._values[0], expression_attribute_names, used_attribute_names); } else { - throw api_error("ValidationException", "KeyConditionExpression must compare attribute with constants"); + throw api_error::validation("KeyConditionExpression must compare attribute with constants"); } break; default: @@ -3187,15 +3186,15 @@ calculate_bounds_condition_expression(schema_ptr schema, throw std::logic_error(format("Wrong number of values {} in primitive_condition", cond._values.size())); } if (cond._op == parsed::primitive_condition::type::IN) { - throw api_error("ValidationException", "KeyConditionExpression does not support IN operator"); + throw api_error::validation("KeyConditionExpression does not support IN operator"); } else if (cond._op == parsed::primitive_condition::type::NE) { - throw api_error("ValidationException", "KeyConditionExpression does not support NE operator"); + throw api_error::validation("KeyConditionExpression does not support NE operator"); } else if (cond._op == parsed::primitive_condition::type::EQ) { // the EQ operator (=) is the only one which can be used for both // the partition key and sort key: if (sstring(key) == pk_cdef.name_as_text()) { if (!partition_ranges.empty()) { - throw api_error("ValidationException", + throw api_error::validation( "KeyConditionExpression allows only one condition for each key"); } bytes raw_value = get_constant_value(cond._values[!toplevel_ind], pk_cdef); @@ -3204,14 +3203,14 @@ calculate_bounds_condition_expression(schema_ptr schema, partition_ranges.push_back(dht::partition_range(decorated_key)); } else if (ck_cdef && sstring(key) == ck_cdef->name_as_text()) { if (!ck_bounds.empty()) { - throw api_error("ValidationException", + throw api_error::validation( "KeyConditionExpression allows only one condition for each key"); } bytes raw_value = get_constant_value(cond._values[!toplevel_ind], *ck_cdef); clustering_key ck = clustering_key::from_single_value(*schema, raw_value); ck_bounds.push_back(query::clustering_range(ck)); } else { - throw api_error("ValidationException", + throw api_error::validation( format("KeyConditionExpression condition on non-key attribute {}", key)); } continue; @@ -3219,14 +3218,14 @@ calculate_bounds_condition_expression(schema_ptr schema, // If we're still here, it's any other operator besides EQ, and these // are allowed *only* on the clustering key: if (sstring(key) == pk_cdef.name_as_text()) { - throw api_error("ValidationException", + throw api_error::validation( format("KeyConditionExpression only '=' condition is supported on partition key {}", key)); } else if (!ck_cdef || sstring(key) != ck_cdef->name_as_text()) { - throw api_error("ValidationException", + throw api_error::validation( format("KeyConditionExpression condition on non-key attribute {}", key)); } if (!ck_bounds.empty()) { - throw api_error("ValidationException", + throw api_error::validation( "KeyConditionExpression allows only one condition for each key"); } if (cond._op == parsed::primitive_condition::type::BETWEEN) { @@ -3246,7 +3245,7 @@ calculate_bounds_condition_expression(schema_ptr schema, if (!ck_cdef->type->is_compatible_with(*utf8_type)) { // begins_with() supported on bytes and strings (both stored // in the database as strings) but not on numbers. - throw api_error("ValidationException", + throw api_error::validation( format("KeyConditionExpression begins_with() not supported on type {}", type_to_string(ck_cdef->type))); } else if (raw_value.empty()) { @@ -3279,7 +3278,7 @@ calculate_bounds_condition_expression(schema_ptr schema, } if (partition_ranges.empty()) { - throw api_error("ValidationException", + throw api_error::validation( format("KeyConditionExpression requires a condition on partition key {}", pk_cdef.name_as_text())); } if (ck_bounds.empty()) { @@ -3299,13 +3298,13 @@ future executor::query(client_state& client_state rjson::value* exclusive_start_key = rjson::find(request, "ExclusiveStartKey"); db::consistency_level cl = get_read_consistency(request); if (table_type == table_or_view_type::gsi && cl != db::consistency_level::LOCAL_ONE) { - return make_ready_future(api_error("ValidationException", + return make_ready_future(api_error::validation( "Consistent reads are not allowed on global indexes (GSI)")); } rjson::value* limit_json = rjson::find(request, "Limit"); uint32_t limit = limit_json ? limit_json->GetUint64() : query::max_partitions; if (limit <= 0) { - return make_ready_future(api_error("ValidationException", "Limit must be greater than 0")); + return make_ready_future(api_error::validation("Limit must be greater than 0")); } const bool forward = get_bool_attribute(request, "ScanIndexForward", true); @@ -3315,10 +3314,10 @@ future executor::query(client_state& client_state std::unordered_set used_attribute_values; std::unordered_set used_attribute_names; if (key_conditions && key_condition_expression) { - throw api_error("ValidationException", "Query does not allow both " + throw api_error::validation("Query does not allow both " "KeyConditions and KeyConditionExpression to be given together"); } else if (!key_conditions && !key_condition_expression) { - throw api_error("ValidationException", "Query must have one of " + throw api_error::validation("Query must have one of " "KeyConditions or KeyConditionExpression"); } // exactly one of key_conditions or key_condition_expression @@ -3336,13 +3335,13 @@ future executor::query(client_state& client_state // A query is not allowed to filter on the partition key or the sort key. for (const column_definition& cdef : schema->partition_key_columns()) { // just one if (filter.filters_on(cdef.name_as_text())) { - return make_ready_future(api_error("ValidationException", + return make_ready_future(api_error::validation( format("QueryFilter can only contain non-primary key attributes: Partition key attribute: {}", cdef.name_as_text()))); } } for (const column_definition& cdef : schema->clustering_key_columns()) { if (filter.filters_on(cdef.name_as_text())) { - return make_ready_future(api_error("ValidationException", + return make_ready_future(api_error::validation( format("QueryFilter can only contain non-primary key attributes: Sort key attribute: {}", cdef.name_as_text()))); } // FIXME: this "break" can avoid listing some clustering key columns @@ -3369,7 +3368,7 @@ future executor::list_tables(client_state& client std::string exclusive_start = exclusive_start_json ? exclusive_start_json->GetString() : ""; int limit = limit_json ? limit_json->GetInt() : 100; if (limit < 1 || limit > 100) { - return make_ready_future(api_error("ValidationException", "Limit must be greater than 0 and no greater than 100")); + return make_ready_future(api_error::validation("Limit must be greater than 0 and no greater than 100")); } auto table_names = _proxy.get_db().local().get_column_families() @@ -3422,7 +3421,7 @@ future executor::describe_endpoints(client_state& // A "Host:" header includes both host name and port, exactly what we need // to return. if (host_header.empty()) { - return make_ready_future(api_error("ValidationException", "DescribeEndpoints needs a 'Host:' header in request")); + return make_ready_future(api_error::validation("DescribeEndpoints needs a 'Host:' header in request")); } rjson::set(response, "Endpoints", rjson::empty_array()); rjson::push_back(response["Endpoints"], rjson::empty_object()); diff --git a/alternator/expressions.cc b/alternator/expressions.cc index d7f131b947..3e8bbfa470 100644 --- a/alternator/expressions.cc +++ b/alternator/expressions.cc @@ -157,12 +157,12 @@ static void resolve_path(parsed::path& p, const std::string& column_name = p.root(); if (column_name.size() > 0 && column_name.front() == '#') { if (!expression_attribute_names) { - throw api_error("ValidationException", + throw api_error::validation( format("ExpressionAttributeNames missing, entry '{}' required by expression", column_name)); } const rjson::value* value = rjson::find(*expression_attribute_names, column_name); if (!value || !value->IsString()) { - throw api_error("ValidationException", + throw api_error::validation( format("ExpressionAttributeNames missing entry '{}' required by expression", column_name)); } used_attribute_names.emplace(column_name); @@ -176,16 +176,16 @@ static void resolve_constant(parsed::constant& c, std::visit(overloaded_functor { [&] (const std::string& valref) { if (!expression_attribute_values) { - throw api_error("ValidationException", + throw api_error::validation( format("ExpressionAttributeValues missing, entry '{}' required by expression", valref)); } const rjson::value* value = rjson::find(*expression_attribute_values, valref); if (!value) { - throw api_error("ValidationException", + throw api_error::validation( format("ExpressionAttributeValues missing entry '{}' required by expression", valref)); } if (value->IsNull()) { - throw api_error("ValidationException", + throw api_error::validation( format("ExpressionAttributeValues null value for entry '{}' required by expression", valref)); } validate_value(*value, "ExpressionAttributeValues"); @@ -359,7 +359,7 @@ static rjson::value list_concatenate(const rjson::value& v1, const rjson::value& const rjson::value* list1 = unwrap_list(v1); const rjson::value* list2 = unwrap_list(v2); if (!list1 || !list2) { - throw api_error("ValidationException", "UpdateExpression: list_append() given a non-list"); + throw api_error::validation("UpdateExpression: list_append() given a non-list"); } rjson::value cat = rjson::copy(*list1); for (const auto& a : list2->GetArray()) { @@ -380,28 +380,28 @@ static rjson::value calculate_size(const rjson::value& v) { // must come from the request itself, not from the database, so it makes // sense to throw a ValidationException if we see such a problem. if (!v.IsObject() || v.MemberCount() != 1) { - throw api_error("ValidationException", format("invalid object: {}", v)); + throw api_error::validation(format("invalid object: {}", v)); } auto it = v.MemberBegin(); int ret; if (it->name == "S") { if (!it->value.IsString()) { - throw api_error("ValidationException", format("invalid string: {}", v)); + throw api_error::validation(format("invalid string: {}", v)); } ret = it->value.GetStringLength(); } else if (it->name == "NS" || it->name == "SS" || it->name == "BS" || it->name == "L") { if (!it->value.IsArray()) { - throw api_error("ValidationException", format("invalid set: {}", v)); + throw api_error::validation(format("invalid set: {}", v)); } ret = it->value.Size(); } else if (it->name == "M") { if (!it->value.IsObject()) { - throw api_error("ValidationException", format("invalid map: {}", v)); + throw api_error::validation(format("invalid map: {}", v)); } ret = it->value.MemberCount(); } else if (it->name == "B") { if (!it->value.IsString()) { - throw api_error("ValidationException", format("invalid byte string: {}", v)); + throw api_error::validation(format("invalid byte string: {}", v)); } ret = base64_decoded_len(rjson::to_string_view(it->value)); } else { @@ -445,11 +445,11 @@ static const std::unordered_map function_handlers { {"list_append", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) { if (caller != calculate_value_caller::UpdateExpression) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: list_append() not allowed here", caller)); } if (f._parameters.size() != 2) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: list_append() accepts 2 parameters, got {}", caller, f._parameters.size())); } rjson::value v1 = calculate_value(f._parameters[0], caller, previous_item); @@ -459,15 +459,15 @@ std::unordered_map function_handlers { }, {"if_not_exists", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) { if (caller != calculate_value_caller::UpdateExpression) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: if_not_exists() not allowed here", caller)); } if (f._parameters.size() != 2) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: if_not_exists() accepts 2 parameters, got {}", caller, f._parameters.size())); } if (!std::holds_alternative(f._parameters[0]._value)) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: if_not_exists() must include path as its first argument", caller)); } rjson::value v1 = calculate_value(f._parameters[0], caller, previous_item); @@ -477,11 +477,11 @@ std::unordered_map function_handlers { }, {"size", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) { if (caller != calculate_value_caller::ConditionExpression) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: size() not allowed here", caller)); } if (f._parameters.size() != 1) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: size() accepts 1 parameter, got {}", caller, f._parameters.size())); } rjson::value v = calculate_value(f._parameters[0], caller, previous_item); @@ -490,15 +490,15 @@ std::unordered_map function_handlers { }, {"attribute_exists", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) { if (caller != calculate_value_caller::ConditionExpressionAlone) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: attribute_exists() not allowed here", caller)); } if (f._parameters.size() != 1) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: attribute_exists() accepts 1 parameter, got {}", caller, f._parameters.size())); } if (!std::holds_alternative(f._parameters[0]._value)) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: attribute_exists()'s parameter must be a path", caller)); } rjson::value v = calculate_value(f._parameters[0], caller, previous_item); @@ -507,15 +507,15 @@ std::unordered_map function_handlers { }, {"attribute_not_exists", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) { if (caller != calculate_value_caller::ConditionExpressionAlone) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: attribute_not_exists() not allowed here", caller)); } if (f._parameters.size() != 1) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: attribute_not_exists() accepts 1 parameter, got {}", caller, f._parameters.size())); } if (!std::holds_alternative(f._parameters[0]._value)) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: attribute_not_exists()'s parameter must be a path", caller)); } rjson::value v = calculate_value(f._parameters[0], caller, previous_item); @@ -524,18 +524,18 @@ std::unordered_map function_handlers { }, {"attribute_type", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) { if (caller != calculate_value_caller::ConditionExpressionAlone) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: attribute_type() not allowed here", caller)); } if (f._parameters.size() != 2) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: attribute_type() accepts 2 parameters, got {}", caller, f._parameters.size())); } // There is no real reason for the following check (not // allowing the type to come from a document attribute), but // DynamoDB does this check, so we do too... if (!f._parameters[1].is_constant()) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: attribute_types()'s first parameter must be an expression attribute", caller)); } rjson::value v0 = calculate_value(f._parameters[0], caller, previous_item); @@ -544,7 +544,7 @@ std::unordered_map function_handlers { // If the type parameter is not one of the legal types // we should generate an error, not a failed condition: if (!known_type(rjson::to_string_view(v1.MemberBegin()->value))) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: attribute_types()'s second parameter, {}, is not a known type", caller, v1.MemberBegin()->value)); } @@ -554,18 +554,18 @@ std::unordered_map function_handlers { return to_bool_json(false); } } else { - throw api_error("ValidationException", + throw api_error::validation( format("{}: attribute_type() second parameter must refer to a string, got {}", caller, v1)); } } }, {"begins_with", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) { if (caller != calculate_value_caller::ConditionExpressionAlone) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: begins_with() not allowed here", caller)); } if (f._parameters.size() != 2) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: begins_with() accepts 2 parameters, got {}", caller, f._parameters.size())); } rjson::value v1 = calculate_value(f._parameters[0], caller, previous_item); @@ -582,23 +582,23 @@ std::unordered_map function_handlers { if (!v1.IsObject() || v1.MemberCount() != 1) { bad = true; if (f._parameters[0].is_constant()) { - throw api_error("ValidationException", format("{}: begins_with() encountered malformed AttributeValue: {}", caller, v1)); + throw api_error::validation(format("{}: begins_with() encountered malformed AttributeValue: {}", caller, v1)); } } else if (v1.MemberBegin()->name != "S" && v1.MemberBegin()->name != "B") { bad = true; if (f._parameters[0].is_constant()) { - throw api_error("ValidationException", format("{}: begins_with() supports only string or binary in AttributeValue: {}", caller, v1)); + throw api_error::validation(format("{}: begins_with() supports only string or binary in AttributeValue: {}", caller, v1)); } } if (!v2.IsObject() || v2.MemberCount() != 1) { bad = true; if (f._parameters[1].is_constant()) { - throw api_error("ValidationException", format("{}: begins_with() encountered malformed AttributeValue: {}", caller, v2)); + throw api_error::validation(format("{}: begins_with() encountered malformed AttributeValue: {}", caller, v2)); } } else if (v2.MemberBegin()->name != "S" && v2.MemberBegin()->name != "B") { bad = true; if (f._parameters[1].is_constant()) { - throw api_error("ValidationException", format("{}: begins_with() supports only string or binary in AttributeValue: {}", caller, v2)); + throw api_error::validation(format("{}: begins_with() supports only string or binary in AttributeValue: {}", caller, v2)); } } bool ret = false; @@ -620,11 +620,11 @@ std::unordered_map function_handlers { }, {"contains", [] (calculate_value_caller caller, const rjson::value* previous_item, const parsed::value::function_call& f) { if (caller != calculate_value_caller::ConditionExpressionAlone) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: contains() not allowed here", caller)); } if (f._parameters.size() != 2) { - throw api_error("ValidationException", + throw api_error::validation( format("{}: contains() accepts 2 parameters, got {}", caller, f._parameters.size())); } rjson::value v1 = calculate_value(f._parameters[0], caller, previous_item); @@ -650,7 +650,7 @@ rjson::value calculate_value(const parsed::value& v, [&] (const parsed::value::function_call& f) -> rjson::value { auto function_it = function_handlers.find(std::string_view(f._function_name)); if (function_it == function_handlers.end()) { - throw api_error("ValidationException", + throw api_error::validation( format("UpdateExpression: unknown function '{}' called.", f._function_name)); } return function_it->second(caller, previous_item, f); @@ -662,7 +662,7 @@ rjson::value calculate_value(const parsed::value& v, std::string update_path = p.root(); if (p.has_operators()) { // FIXME: support this - throw api_error("ValidationException", "Reading attribute paths not yet implemented"); + throw api_error::validation("Reading attribute paths not yet implemented"); } const rjson::value* previous_value = rjson::find(*previous_item, update_path); return previous_value ? rjson::copy(*previous_value) : rjson::null_value(); diff --git a/alternator/serialization.cc b/alternator/serialization.cc index 279ccd3697..ddce3c1533 100644 --- a/alternator/serialization.cc +++ b/alternator/serialization.cc @@ -77,7 +77,7 @@ struct from_json_visitor { try { bo.write(t.from_string(rjson::to_string_view(v))); } catch (const marshal_exception& e) { - throw api_error("ValidationException", format("The parameter cannot be converted to a numeric value: {}", v)); + throw api_error::validation(format("The parameter cannot be converted to a numeric value: {}", v)); } } // default @@ -88,7 +88,7 @@ struct from_json_visitor { bytes serialize_item(const rjson::value& item) { if (item.IsNull() || item.MemberCount() != 1) { - throw api_error("ValidationException", format("An item can contain only one attribute definition: {}", item)); + throw api_error::validation(format("An item can contain only one attribute definition: {}", item)); } auto it = item.MemberBegin(); type_info type_info = type_info_from_string(rjson::to_string_view(it->name)); // JSON keys are guaranteed to be strings @@ -132,7 +132,7 @@ struct to_json_visitor { rjson::value deserialize_item(bytes_view bv) { rjson::value deserialized(rapidjson::kObjectType); if (bv.empty()) { - throw api_error("ValidationException", "Serialized value empty"); + throw api_error::validation("Serialized value empty"); } alternator_type atype = alternator_type(bv[0]); @@ -168,7 +168,7 @@ bytes get_key_column_value(const rjson::value& item, const column_definition& co std::string column_name = column.name_as_text(); const rjson::value* key_typed_value = rjson::find(item, column_name); if (!key_typed_value) { - throw api_error("ValidationException", format("Key column {} not found", column_name)); + throw api_error::validation(format("Key column {} not found", column_name)); } return get_key_from_typed_value(*key_typed_value, column); } @@ -179,20 +179,20 @@ bytes get_key_column_value(const rjson::value& item, const column_definition& co bytes get_key_from_typed_value(const rjson::value& key_typed_value, const column_definition& column) { if (!key_typed_value.IsObject() || key_typed_value.MemberCount() != 1 || !key_typed_value.MemberBegin()->value.IsString()) { - throw api_error("ValidationException", + throw api_error::validation( format("Malformed value object for key column {}: {}", column.name_as_text(), key_typed_value)); } auto it = key_typed_value.MemberBegin(); if (it->name != type_to_string(column.type)) { - throw api_error("ValidationException", + throw api_error::validation( format("Type mismatch: expected type {} for key column {}, got type {}", type_to_string(column.type), column.name_as_text(), it->name)); } std::string_view value_view = rjson::to_string_view(it->value); if (value_view.empty()) { - throw api_error("ValidationException", + throw api_error::validation( format("The AttributeValue for a key attribute cannot contain an empty string value. Key: {}", column.name_as_text())); } if (column.type == bytes_type) { @@ -251,11 +251,11 @@ clustering_key ck_from_json(const rjson::value& item, schema_ptr schema) { big_decimal unwrap_number(const rjson::value& v, std::string_view diagnostic) { if (!v.IsObject() || v.MemberCount() != 1) { - throw api_error("ValidationException", format("{}: invalid number object", diagnostic)); + throw api_error::validation(format("{}: invalid number object", diagnostic)); } auto it = v.MemberBegin(); if (it->name != "N") { - throw api_error("ValidationException", format("{}: expected number, found type '{}'", diagnostic, it->name)); + throw api_error::validation(format("{}: expected number, found type '{}'", diagnostic, it->name)); } try { if (it->value.IsNumber()) { @@ -263,11 +263,11 @@ big_decimal unwrap_number(const rjson::value& v, std::string_view diagnostic) { return big_decimal(rjson::print(it->value)); } if (!it->value.IsString()) { - throw api_error("ValidationException", format("{}: improperly formatted number constant", diagnostic)); + throw api_error::validation(format("{}: improperly formatted number constant", diagnostic)); } return big_decimal(rjson::to_string_view(it->value)); } catch (const marshal_exception& e) { - throw api_error("ValidationException", format("The parameter cannot be converted to a numeric value: {}", it->value)); + throw api_error::validation(format("The parameter cannot be converted to a numeric value: {}", it->value)); } } @@ -320,10 +320,10 @@ rjson::value set_sum(const rjson::value& v1, const rjson::value& v2) { auto [set1_type, set1] = unwrap_set(v1); auto [set2_type, set2] = unwrap_set(v2); if (set1_type != set2_type) { - throw api_error("ValidationException", format("Mismatched set types: {} and {}", set1_type, set2_type)); + throw api_error::validation(format("Mismatched set types: {} and {}", set1_type, set2_type)); } if (!set1 || !set2) { - throw api_error("ValidationException", "UpdateExpression: ADD operation for sets must be given sets as arguments"); + throw api_error::validation("UpdateExpression: ADD operation for sets must be given sets as arguments"); } rjson::value sum = rjson::copy(*set1); std::set set1_raw; @@ -348,10 +348,10 @@ std::optional set_diff(const rjson::value& v1, const rjson::value& auto [set1_type, set1] = unwrap_set(v1); auto [set2_type, set2] = unwrap_set(v2); if (set1_type != set2_type) { - throw api_error("ValidationException", format("Mismatched set types: {} and {}", set1_type, set2_type)); + throw api_error::validation(format("Mismatched set types: {} and {}", set1_type, set2_type)); } if (!set1 || !set2) { - throw api_error("ValidationException", "UpdateExpression: DELETE operation can only be performed on a set"); + throw api_error::validation("UpdateExpression: DELETE operation can only be performed on a set"); } std::set set1_raw; for (auto it = set1->Begin(); it != set1->End(); ++it) { diff --git a/alternator/server.cc b/alternator/server.cc index 6682e20546..4ed2cce7e2 100644 --- a/alternator/server.cc +++ b/alternator/server.cc @@ -75,20 +75,17 @@ public: // returned to the client as expected. Other types of // exceptions are unexpected, and returned to the user // as an internal server error: - api_error ret; try { resf.get(); } catch (api_error &ae) { - ret = ae; + generate_error_reply(*rep, ae); } catch (rjson::error & re) { - ret = api_error("ValidationException", re.what()); + generate_error_reply(*rep, + api_error::validation(re.what())); } catch (...) { - ret = api_error( - "Internal Server Error", - format("Internal server error: {}", std::current_exception()), - reply::status_type::internal_server_error); + generate_error_reply(*rep, + api_error::internal(format("Internal server error: {}", std::current_exception()))); } - generate_error_reply(*rep, ret); return make_ready_future>(std::move(rep)); } auto res = resf.get0(); @@ -188,11 +185,11 @@ future<> server::verify_signature(const request& req) { } auto host_it = req._headers.find("Host"); if (host_it == req._headers.end()) { - throw api_error("InvalidSignatureException", "Host header is mandatory for signature verification"); + throw api_error::invalid_signature("Host header is mandatory for signature verification"); } auto authorization_it = req._headers.find("Authorization"); if (authorization_it == req._headers.end()) { - throw api_error("InvalidSignatureException", "Authorization header is mandatory for signature verification"); + throw api_error::invalid_signature("Authorization header is mandatory for signature verification"); } std::string host = host_it->second; std::vector credentials_raw = split(authorization_it->second, ' '); @@ -204,7 +201,7 @@ future<> server::verify_signature(const request& req) { std::vector entry_split = split(entry, '='); if (entry_split.size() != 2) { if (entry != "AWS4-HMAC-SHA256") { - throw api_error("InvalidSignatureException", format("Only AWS4-HMAC-SHA256 algorithm is supported. Found: {}", entry)); + throw api_error::invalid_signature(format("Only AWS4-HMAC-SHA256 algorithm is supported. Found: {}", entry)); } continue; } @@ -225,7 +222,7 @@ future<> server::verify_signature(const request& req) { } std::vector credential_split = split(credential, '/'); if (credential_split.size() != 5) { - throw api_error("ValidationException", format("Incorrect credential information format: {}", credential)); + throw api_error::validation(format("Incorrect credential information format: {}", credential)); } std::string user(credential_split[0]); std::string datestamp(credential_split[1]); @@ -263,7 +260,7 @@ future<> server::verify_signature(const request& req) { if (signature != std::string_view(user_signature)) { _key_cache.remove(user); - throw api_error("UnrecognizedClientException", "The security token included in the request is invalid."); + throw api_error::unrecognized_client("The security token included in the request is invalid."); } }); } @@ -279,8 +276,7 @@ future server::handle_api_request(std::unique_ptr auto callback_it = _callbacks.find(op); if (callback_it == _callbacks.end()) { _executor._stats.unsupported_operations++; - throw api_error("UnknownOperationException", - format("Unsupported operation {}", op)); + throw api_error::unknown_operation(format("Unsupported operation {}", op)); } return with_gate(_pending_requests, [this, callback_it = std::move(callback_it), op = std::move(op), req = std::move(req)] () mutable { //FIXME: Client state can provide more context, e.g. client's endpoint address diff --git a/alternator/streams.cc b/alternator/streams.cc index af480c54c2..8fa3e5a8b5 100644 --- a/alternator/streams.cc +++ b/alternator/streams.cc @@ -139,7 +139,7 @@ future alternator::executor::list_str auto e = cfs.end(); if (limit < 1) { - throw validation_exception("Limit must be 1 or more"); + throw api_error::validation("Limit must be 1 or more"); } // TODO: the unordered_map here is not really well suited for partial @@ -372,11 +372,11 @@ future executor::describe_stream(client_state& cl } if (!schema || !bs || !is_alternator_keyspace(schema->ks_name())) { - throw resource_not_found_exception("Invalid StreamArn"); + throw api_error::resource_not_found("Invalid StreamArn"); } if (limit < 1) { - throw validation_exception("Limit must be 1 or more"); + throw api_error::validation("Limit must be 1 or more"); } std::optional shard_start; @@ -568,10 +568,10 @@ future executor::get_shard_iterator(client_state& auto seq_num = rjson::get_opt(request, "SequenceNumber"); if (type < shard_iterator_type::TRIM_HORIZON && !seq_num) { - throw validation_exception("Missing required parameter \"SequenceNumber\""); + throw api_error::validation("Missing required parameter \"SequenceNumber\""); } if (type >= shard_iterator_type::TRIM_HORIZON && seq_num) { - throw validation_exception("Iterator of this type should not have sequence number"); + throw api_error::validation("Iterator of this type should not have sequence number"); } auto stream_arn = rjson::get(request, "StreamArn"); @@ -587,7 +587,7 @@ future executor::get_shard_iterator(client_state& } catch (...) { } if (!schema || !cdc::get_base_table(db, *schema) || !is_alternator_keyspace(schema->ks_name()) || sid->table != schema->id()) { - throw resource_not_found_exception("Invalid StreamArn"); + throw api_error::resource_not_found("Invalid StreamArn"); } utils::UUID threshold; @@ -650,7 +650,7 @@ future executor::get_records(client_state& client auto limit = rjson::get_opt(request, "Limit").value_or(1000); if (limit < 1) { - throw validation_exception("Limit must be 1 or more"); + throw api_error::validation("Limit must be 1 or more"); } auto& db = _proxy.get_db().local(); @@ -663,7 +663,7 @@ future executor::get_records(client_state& client } if (!schema || !base || !is_alternator_keyspace(schema->ks_name())) { - throw resource_not_found_exception(boost::lexical_cast(iter.shard.table)); + throw api_error::resource_not_found(boost::lexical_cast(iter.shard.table)); } tracing::add_table_name(trace_state, schema->ks_name(), schema->cf_name()); @@ -858,7 +858,7 @@ future executor::get_records(client_state& client void executor::add_stream_options(const rjson::value& stream_specification, schema_builder& builder) { auto stream_enabled = rjson::find(stream_specification, "StreamEnabled"); if (!stream_enabled || !stream_enabled->IsBool()) { - throw validation_exception("StreamSpecification needs boolean StreamEnabled"); + throw api_error::validation("StreamSpecification needs boolean StreamEnabled"); } if (stream_enabled->GetBool()) { diff --git a/configure.py b/configure.py index f201f3c747..7381759b67 100755 --- a/configure.py +++ b/configure.py @@ -859,7 +859,6 @@ alternator = [ Antlr3Grammar('alternator/expressions.g'), 'alternator/conditions.cc', 'alternator/auth.cc', - 'alternator/error.cc', 'alternator/streams.cc', ]