Add assets settings document tests
This commit is contained in:
183
src/assets/settings_document.cpp
Normal file
183
src/assets/settings_document.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
#include "assets/settings_document.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cmath>
|
||||
|
||||
namespace pp::assets {
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] bool is_valid_key_char(char value) noexcept
|
||||
{
|
||||
const auto ch = static_cast<unsigned char>(value);
|
||||
return std::isalnum(ch) != 0 || value == '_' || value == '-' || value == '.';
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
std::size_t SettingsDocument::size() const noexcept
|
||||
{
|
||||
return entries_.size();
|
||||
}
|
||||
|
||||
bool SettingsDocument::empty() const noexcept
|
||||
{
|
||||
return entries_.empty();
|
||||
}
|
||||
|
||||
bool SettingsDocument::has(std::string_view key) const noexcept
|
||||
{
|
||||
return find_entry(key) != entries_.end();
|
||||
}
|
||||
|
||||
const std::vector<SettingsEntry>& SettingsDocument::entries() const noexcept
|
||||
{
|
||||
return entries_;
|
||||
}
|
||||
|
||||
pp::foundation::Status SettingsDocument::set(std::string_view key, SettingsValue value)
|
||||
{
|
||||
const auto key_status = validate_settings_key(key);
|
||||
if (!key_status.ok()) {
|
||||
return key_status;
|
||||
}
|
||||
|
||||
const auto value_status = validate_settings_value(value);
|
||||
if (!value_status.ok()) {
|
||||
return value_status;
|
||||
}
|
||||
|
||||
auto found = find_entry(key);
|
||||
if (found != entries_.end()) {
|
||||
found->value = value;
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
if (entries_.size() >= max_settings_entries) {
|
||||
return pp::foundation::Status::out_of_range("settings entry count exceeds the configured limit");
|
||||
}
|
||||
|
||||
entries_.push_back(SettingsEntry {
|
||||
.key = std::string(key),
|
||||
.value = value,
|
||||
});
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Result<SettingsValue> SettingsDocument::get(std::string_view key) const
|
||||
{
|
||||
const auto key_status = validate_settings_key(key);
|
||||
if (!key_status.ok()) {
|
||||
return pp::foundation::Result<SettingsValue>::failure(key_status);
|
||||
}
|
||||
|
||||
const auto found = find_entry(key);
|
||||
if (found == entries_.end()) {
|
||||
return pp::foundation::Result<SettingsValue>::failure(
|
||||
pp::foundation::Status::out_of_range("settings key was not found"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<SettingsValue>::success(found->value);
|
||||
}
|
||||
|
||||
pp::foundation::Status SettingsDocument::unset(std::string_view key) noexcept
|
||||
{
|
||||
const auto key_status = validate_settings_key(key);
|
||||
if (!key_status.ok()) {
|
||||
return key_status;
|
||||
}
|
||||
|
||||
const auto found = find_entry(key);
|
||||
if (found == entries_.end()) {
|
||||
return pp::foundation::Status::out_of_range("settings key was not found");
|
||||
}
|
||||
|
||||
entries_.erase(found);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
void SettingsDocument::clear() noexcept
|
||||
{
|
||||
entries_.clear();
|
||||
}
|
||||
|
||||
std::vector<SettingsEntry>::iterator SettingsDocument::find_entry(std::string_view key) noexcept
|
||||
{
|
||||
return std::find_if(
|
||||
entries_.begin(),
|
||||
entries_.end(),
|
||||
[key](const SettingsEntry& entry) {
|
||||
return entry.key == key;
|
||||
});
|
||||
}
|
||||
|
||||
std::vector<SettingsEntry>::const_iterator SettingsDocument::find_entry(std::string_view key) const noexcept
|
||||
{
|
||||
return std::find_if(
|
||||
entries_.begin(),
|
||||
entries_.end(),
|
||||
[key](const SettingsEntry& entry) {
|
||||
return entry.key == key;
|
||||
});
|
||||
}
|
||||
|
||||
pp::foundation::Status validate_settings_key(std::string_view key) noexcept
|
||||
{
|
||||
if (key.empty()) {
|
||||
return pp::foundation::Status::invalid_argument("settings key must not be empty");
|
||||
}
|
||||
|
||||
if (key.size() > max_settings_key_length) {
|
||||
return pp::foundation::Status::out_of_range("settings key length exceeds the configured limit");
|
||||
}
|
||||
|
||||
if (key.front() == '.' || key.back() == '.') {
|
||||
return pp::foundation::Status::invalid_argument("settings key must not start or end with a dot");
|
||||
}
|
||||
|
||||
for (const auto ch : key) {
|
||||
if (!is_valid_key_char(ch)) {
|
||||
return pp::foundation::Status::invalid_argument("settings key contains an unsupported character");
|
||||
}
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Status validate_settings_value(const SettingsValue& value) noexcept
|
||||
{
|
||||
if (const auto* string_value = std::get_if<std::string>(&value)) {
|
||||
if (string_value->size() > max_settings_string_length) {
|
||||
return pp::foundation::Status::out_of_range("settings string length exceeds the configured limit");
|
||||
}
|
||||
}
|
||||
|
||||
if (const auto* double_value = std::get_if<double>(&value)) {
|
||||
if (!std::isfinite(*double_value)) {
|
||||
return pp::foundation::Status::invalid_argument("settings floating point value must be finite");
|
||||
}
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
const char* settings_value_type_name(const SettingsValue& value) noexcept
|
||||
{
|
||||
if (std::holds_alternative<bool>(value)) {
|
||||
return "bool";
|
||||
}
|
||||
if (std::holds_alternative<std::int64_t>(value)) {
|
||||
return "int64";
|
||||
}
|
||||
if (std::holds_alternative<double>(value)) {
|
||||
return "double";
|
||||
}
|
||||
if (std::holds_alternative<std::string>(value)) {
|
||||
return "string";
|
||||
}
|
||||
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
}
|
||||
48
src/assets/settings_document.h
Normal file
48
src/assets/settings_document.h
Normal file
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <variant>
|
||||
#include <vector>
|
||||
|
||||
namespace pp::assets {
|
||||
|
||||
constexpr std::size_t max_settings_entries = 4096;
|
||||
constexpr std::size_t max_settings_key_length = 128;
|
||||
constexpr std::size_t max_settings_string_length = 4096;
|
||||
|
||||
using SettingsValue = std::variant<bool, std::int64_t, double, std::string>;
|
||||
|
||||
struct SettingsEntry {
|
||||
std::string key;
|
||||
SettingsValue value;
|
||||
};
|
||||
|
||||
class SettingsDocument {
|
||||
public:
|
||||
[[nodiscard]] std::size_t size() const noexcept;
|
||||
[[nodiscard]] bool empty() const noexcept;
|
||||
[[nodiscard]] bool has(std::string_view key) const noexcept;
|
||||
[[nodiscard]] const std::vector<SettingsEntry>& entries() const noexcept;
|
||||
|
||||
[[nodiscard]] pp::foundation::Status set(std::string_view key, SettingsValue value);
|
||||
[[nodiscard]] pp::foundation::Result<SettingsValue> get(std::string_view key) const;
|
||||
[[nodiscard]] pp::foundation::Status unset(std::string_view key) noexcept;
|
||||
void clear() noexcept;
|
||||
|
||||
private:
|
||||
[[nodiscard]] std::vector<SettingsEntry>::iterator find_entry(std::string_view key) noexcept;
|
||||
[[nodiscard]] std::vector<SettingsEntry>::const_iterator find_entry(std::string_view key) const noexcept;
|
||||
|
||||
std::vector<SettingsEntry> entries_;
|
||||
};
|
||||
|
||||
[[nodiscard]] pp::foundation::Status validate_settings_key(std::string_view key) noexcept;
|
||||
[[nodiscard]] pp::foundation::Status validate_settings_value(const SettingsValue& value) noexcept;
|
||||
[[nodiscard]] const char* settings_value_type_name(const SettingsValue& value) noexcept;
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user