#pragma once #include "rpc/Errors.hpp" #include "rpc/common/Types.hpp" #include "rpc/common/ValidationHelpers.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace rpc::validation { /** * @brief A validator that simply requires a field to be present. */ struct Required final { /** * @brief Verify that the JSON value is present and not null. * * @param value The JSON value representing the outer object * @param key The key used to retrieve the tested value from the outer object * @return An error if validation failed; otherwise no error is returned */ [[nodiscard]] static MaybeError verify(boost::json::value const& value, std::string_view key); }; /** * @brief A validator that forbids a field to be present. * * If there is a value provided, it will forbid the field only when the value equals. * If there is no value provided, it will forbid the field when the field shows up. */ template class NotSupported; /** * @brief A specialized NotSupported validator that forbids a field to be present when the value * equals the given value. */ template class NotSupported final { T value_; public: /** * @brief Constructs a new NotSupported validator. * * @param val The value to store and verify against */ NotSupported(T val) : value_(val) { } /** * @brief Verify whether the field is supported or not. * * @param value The JSON value representing the outer object * @param key The key used to retrieve the tested value from the outer object * @return `RippledError::RpcNotSupported` if the value matched; otherwise no error is returned */ [[nodiscard]] MaybeError verify(boost::json::value const& value, std::string_view key) const { if (value.is_object() and value.as_object().contains(key)) { using boost::json::value_to; auto const res = value_to(value.as_object().at(key)); if (value_ == res) { return Error{Status{ RippledError::RpcNotSupported, fmt::format("Not supported field '{}'s value '{}'", std::string{key}, res) }}; } } return {}; } }; /** * @brief A specialized NotSupported validator that forbids a field to be present. */ template <> class NotSupported<> final { public: /** * @brief Verify whether the field is supported or not. * * @param value The JSON value representing the outer object * @param key The key used to retrieve the tested value from the outer object * @return `RippledError::RpcNotSupported` if the field is found; otherwise no error is * returned */ [[nodiscard]] static MaybeError verify(boost::json::value const& value, std::string_view key) { if (value.is_object() and value.as_object().contains(key)) { return Error{Status{ RippledError::RpcNotSupported, "Not supported field '" + std::string{key} + '\'' }}; } return {}; } }; /** * @brief Deduction guide to avoid having to specify the template arguments. */ template NotSupported(T&&... t) -> NotSupported; /** * @brief Validates that the type of the value is one of the given types. */ template struct Type final { /** * @brief Verify that the JSON value is (one) of specified type(s). * @note The value itself can only change for integral types and only if the value is outside of * the range of the expected integer type (see checkTypeAndClamp). * * @param value The JSON value representing the outer object * @param key The key used to retrieve the tested value from the outer object * @return `RippledError::RpcInvalidParams` if validation failed; otherwise no error is * returned */ [[nodiscard]] MaybeError verify(boost::json::value& value, std::string_view key) const { if (not value.is_object() or not value.as_object().contains(key)) return {}; // ignore. If field is supposed to exist, let 'required' fail instead auto& res = value.as_object().at(key); auto const convertible = (checkTypeAndClamp(res) || ...); if (not convertible) return Error{Status{RippledError::RpcInvalidParams}}; return {}; } }; /** * @brief Validate that value is between specified min and max. */ template class Between final { Type min_; Type max_; public: /** * @brief Construct the validator storing min and max values. * * @param min * @param max */ explicit Between(Type min, Type max) : min_{min}, max_{max} { } /** * @brief Verify that the JSON value is within a certain range. * * @param value The JSON value representing the outer object * @param key The key used to retrieve the tested value from the outer object * @return `RippledError::RpcInvalidParams` if validation failed; otherwise no error is * returned */ [[nodiscard]] MaybeError verify(boost::json::value const& value, std::string_view key) const { using boost::json::value_to; if (not value.is_object() or not value.as_object().contains(key)) return {}; // ignore. field does not exist, let 'required' fail instead auto const res = value_to(value.as_object().at(key)); // TODO: may want a way to make this code more generic (e.g. use a free // function that can be overridden for this comparison) if (res < min_ || res > max_) return Error{Status{RippledError::RpcInvalidParams}}; return {}; } }; /** * @brief Validate that value is equal or greater than the specified min. */ template class Min final { Type min_; public: /** * @brief Construct the validator storing min value. * * @param min */ explicit Min(Type min) : min_{min} { } /** * @brief Verify that the JSON value is not smaller than min * * @param value The JSON value representing the outer object * @param key The key used to retrieve the tested value from the outer object * @return `RippledError::RpcInvalidParams` if validation failed; otherwise no error is * returned */ [[nodiscard]] MaybeError verify(boost::json::value const& value, std::string_view key) const { using boost::json::value_to; if (not value.is_object() or not value.as_object().contains(key)) return {}; // ignore. field does not exist, let 'required' fail instead auto const res = value_to(value.as_object().at(key)); if (res < min_) return Error{Status{RippledError::RpcInvalidParams}}; return {}; } }; /** * @brief Validate that value is not greater than max. */ template class Max final { Type max_; public: /** * @brief Construct the validator storing max value. * * @param max */ explicit Max(Type max) : max_{max} { } /** * @brief Verify that the JSON value is not greater than max. * * @param value The JSON value representing the outer object * @param key The key used to retrieve the tested value from the outer object * @return `RippledError::RpcInvalidParams` if validation failed; otherwise no error is * returned */ [[nodiscard]] MaybeError verify(boost::json::value const& value, std::string_view key) const { using boost::json::value_to; if (not value.is_object() or not value.as_object().contains(key)) return {}; // ignore. field does not exist, let 'required' fail instead auto const res = value_to(value.as_object().at(key)); if (res > max_) return Error{Status{RippledError::RpcInvalidParams}}; return {}; } }; /** * @brief Validate that value can be converted to time according to the given format. */ class TimeFormatValidator final { std::string format_; public: /** * @brief Construct the validator storing format value. * * @param format The format to use for time conversion */ explicit TimeFormatValidator(std::string format) : format_{std::move(format)} { } /** * @brief Verify that the JSON value is valid formatted time. * * @param value The JSON value representing the outer object * @param key The key used to retrieve the tested value from the outer object * @return `RippledError::RpcInvalidParams` if validation failed; otherwise no error is * returned */ [[nodiscard]] MaybeError verify(boost::json::value const& value, std::string_view key) const; }; /** * @brief Validates that the value is equal to the one passed in. */ template class EqualTo final { Type original_; public: /** * @brief Construct the validator with stored original value. * * @param original The original value to store */ explicit EqualTo(Type original) : original_{std::move(original)} { } /** * @brief Verify that the JSON value is equal to the stored original. * * @param value The JSON value representing the outer object * @param key The key used to retrieve the tested value from the outer object * @return `RippledError::RpcInvalidParams` if validation failed; otherwise no error is * returned */ [[nodiscard]] MaybeError verify(boost::json::value const& value, std::string_view key) const { using boost::json::value_to; if (not value.is_object() or not value.as_object().contains(key)) return {}; // ignore. field does not exist, let 'required' fail instead auto const res = value_to(value.as_object().at(key)); if (res != original_) return Error{Status{RippledError::RpcInvalidParams}}; return {}; } }; /** * @brief Deduction guide to help disambiguate what it means to EqualTo a "string" without * specifying the type. */ EqualTo(char const*) -> EqualTo; /** * @brief Validates that the value is one of the values passed in. */ template class OneOf final { std::vector options_; public: /** * @brief Construct the validator with stored options of initializer list. * * @param options The list of allowed options */ explicit OneOf(std::initializer_list options) : options_{options} { } /** * @brief Construct the validator with stored options of other container. * * @param begin,end the range to copy the elements from */ template explicit OneOf(InputIt begin, InputIt end) : options_{begin, end} { } /** * @brief Verify that the JSON value is one of the stored options. * * @param value The JSON value representing the outer object * @param key The key used to retrieve the tested value from the outer object * @return `RippledError::RpcInvalidParams` if validation failed; otherwise no error is * returned */ [[nodiscard]] MaybeError verify(boost::json::value const& value, std::string_view key) const { using boost::json::value_to; if (not value.is_object() or not value.as_object().contains(key)) return {}; // ignore. field does not exist, let 'required' fail instead auto const res = value_to(value.as_object().at(key)); if (std::find(std::begin(options_), std::end(options_), res) == std::end(options_)) { return Error{ Status{RippledError::RpcInvalidParams, fmt::format("Invalid field '{}'.", key)} }; } return {}; } }; /** * @brief Deduction guide to help disambiguate what it means to OneOf a few "strings" without * specifying the type. */ OneOf(std::initializer_list) -> OneOf; /** * @brief A meta-validator that allows to specify a custom validation function. */ class CustomValidator final { std::function validator_; public: /** * @brief Constructs a custom validator from any supported callable. * * @tparam Fn The type of callable * @param fn The callable/function object */ template requires std::invocable explicit CustomValidator(Fn&& fn) : validator_{std::forward(fn)} { } /** * @brief Verify that the JSON value is valid according to the custom validation function * stored. * * @param value The JSON value representing the outer object * @param key The key used to retrieve the tested value from the outer object * @return Any compatible user-provided error if validation failed; otherwise no error is * returned */ [[nodiscard]] MaybeError verify(boost::json::value const& value, std::string_view key) const; }; /** * @brief Helper function to check if input value is an uint32 number or not. * * @param sv The input value as a string_view * @return true if the string can be converted to a uint32; false otherwise */ [[nodiscard]] bool checkIsU32Numeric(std::string_view sv); template requires( std::is_same_v || std::is_same_v || std::is_same_v ) MaybeError makeHexStringValidator(boost::json::value const& value, std::string_view key) { if (!value.is_string()) return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "NotString"}}; HexType parsedInt; if (!parsedInt.parseHex(value.as_string().c_str())) return Error{Status{RippledError::RpcInvalidParams, std::string(key) + "Malformed"}}; return MaybeError{}; } /** * @brief A group of custom validation functions */ struct CustomValidators final { /** * @brief Provides a commonly used validator for ledger index. * * LedgerIndex must be a string or an int. If the specified LedgerIndex is a string, its value * must be either "validated" or a valid integer value represented as a string. */ static CustomValidator ledgerIndexValidator; /** * @brief Provides a validator for ledger type. * * A type accepts canonical names of ledger entry types (case insensitive) or short names. * Used by ledger_data. */ static CustomValidator ledgerTypeValidator; /** * @brief Provides a commonly used validator for accounts. * * Account must be a string and the converted public key is valid. */ static CustomValidator accountValidator; /** * @brief Provides a commonly used validator for accounts. * * Account must be a string and can convert to base58. */ static CustomValidator accountBase58Validator; /** * @brief Provides a commonly used validator for markers. * * A marker is composed of a comma-separated index and a start hint. * The former will be read as hex, and the latter can be cast to uint64. */ static CustomValidator accountMarkerValidator; /** * @brief Provides a validator for account type. * * A type accepts canonical names of owned ledger entry types (case insensitive) or short names. * Used by account_objects. */ static CustomValidator accountTypeValidator; /** * @brief Provides a commonly used validator for uint160(AccountID) hex string. * * It must be a string and also a decodable hex. * AccountID uses this validator. */ static CustomValidator uint160HexStringValidator; /** * @brief Provides a commonly used validator for uint192 hex string. * * It must be a string and also a decodable hex. * MPTIssuanceID uses this validator. */ static CustomValidator uint192HexStringValidator; /** * @brief Provides a commonly used validator for uint256 hex string. * * It must be a string and also a decodable hex. * Transaction index, ledger hash all use this validator. */ static CustomValidator uint256HexStringValidator; /** * @brief Provides a commonly used validator for currency, including standard currency code and * token code. */ static CustomValidator currencyValidator; /** * @brief Provides a commonly used validator for issuer type. * * It must be a hex string or base58 string. */ static CustomValidator issuerValidator; /** * @brief Provides a validator for validating streams used in subscribe/unsubscribe. */ static CustomValidator subscribeStreamValidator; /** * @brief Provides a validator for validating accounts used in subscribe/unsubscribe. */ static CustomValidator subscribeAccountsValidator; /** * @brief Validates an asset (xrpl::Issue). * * Used by amm_info. */ static CustomValidator currencyIssueValidator; /** * @brief Provides a validator for validating authorized_credentials json array. * * Used by deposit_preauth. */ static CustomValidator authorizeCredentialValidator; /** * @brief Provides a validator for validating credential_type. * * Used by AuthorizeCredentialValidator in deposit_preauth and by the credential * object lookup in ledger_entry. */ static CustomValidator credentialTypeValidator; }; /** * @brief Validates that the elements of the array is of type Hex256 uint */ struct Hex256ItemType final { /** * @brief Validates given the prerequisite that the type of the json value is an array, * verifies all values within the array is of uint256 hash * * @param value the value to verify * @param key The key used to retrieve the tested value from the outer object * @return `RippledError::RpcInvalidParams` if validation failed; otherwise no error is * returned */ [[nodiscard]] static MaybeError verify(boost::json::value const& value, std::string_view key) { if (not value.is_object() or not value.as_object().contains(key)) return {}; // ignore. If field is supposed to exist, let 'required' fail instead auto const& res = value.as_object().at(key); // loop through each item in the array and make sure it is uint256 hex string for (auto const& elem : res.as_array()) { xrpl::uint256 num; if (!elem.is_string() || !num.parseHex(elem.as_string())) { return Error{ Status{RippledError::RpcInvalidParams, "Item is not a valid uint256 type."} }; } } return {}; } }; } // namespace rpc::validation