feat(structural rules)

This commit is contained in:
whaffman 2025-10-06 16:00:36 +02:00
parent 010ee3e8b6
commit 4f89a2918c
11 changed files with 347 additions and 1 deletions

View File

@ -4,6 +4,7 @@
#include <webserv/config/validation/directive_rules/AValidationRule.hpp> // for AValidationRule
#include <webserv/config/validation/directive_rules/AllowedValuesRule.hpp> // for AllowedValuesRule
#include <webserv/config/validation/directive_rules/PortValidationRule.hpp> // for PortValidationRule
#include <webserv/config/validation/structural_rules/StructuralRules.hpp> // for structural rules
#include <webserv/log/Log.hpp> // for LOCATION, Log
#include <string> // for basic_string, string
@ -12,6 +13,11 @@ ConfigValidator::ConfigValidator(const GlobalConfig *config) : engine_(std::make
{
Log::trace(LOCATION);
/*Structural Rules*/
engine_->addStructuralRule(std::make_unique<MinimumServerBlocksRule>(1));
engine_->addStructuralRule(std::make_unique<RequiredLocationBlocksRule>(1));
engine_->addStructuralRule(std::make_unique<UniqueServerNamesRule>());
/*Global Directive Rules*/
/*Server Directive Rules*/

View File

@ -28,6 +28,14 @@ void ValidationEngine::addLocationRule(const std::string &directiveName, std::un
addRule(locationRules_, directiveName, std::move(rule));
}
void ValidationEngine::addStructuralRule(std::unique_ptr<AStructuralValidationRule> rule)
{
Log::trace(LOCATION);
if (rule != nullptr) {
structuralRules_.push_back(std::move(rule));
}
}
void ValidationEngine::addRule(RuleMap &ruleMap, const std::string &directiveName,
std::unique_ptr<AValidationRule> rule)
{
@ -68,7 +76,7 @@ std::vector<ValidationResult> ValidationEngine::getWarnings() const
bool ValidationEngine::hasErrors() const
{
for (const auto &result : results_)
for (const auto &result : results_) //NOLINT(readability-use-anyofallof)
{
if (!result.isValidResult())
{
@ -114,13 +122,44 @@ void ValidationEngine::validateConfig(RuleMap const &rulesMap, const AConfig *co
void ValidationEngine::validateLocationConfig(const std::string &path, const LocationConfig *config)
{
Log::trace(LOCATION);
// Run location structural validation rules
for (const auto &rule : structuralRules_) {
try {
ValidationResult result = rule->validateLocation(config);
if (!result.isValidResult()) {
results_.push_back(result);
}
} catch (const std::exception &e) {
results_.push_back(ValidationResult::error(
"Structural rule '" + rule->getRuleName() + "' threw exception for location '" + path + "': " + e.what()));
}
}
// Run location directive validation rules
validateConfig(locationRules_, config);
}
void ValidationEngine::validateServerConfig(const ServerConfig *config)
{
Log::trace(LOCATION);
// Run server structural validation rules
for (const auto &rule : structuralRules_) {
try {
ValidationResult result = rule->validateServer(config);
if (!result.isValidResult()) {
results_.push_back(result);
}
} catch (const std::exception &e) {
results_.push_back(ValidationResult::error(
"Structural rule '" + rule->getRuleName() + "' threw exception: " + e.what()));
}
}
// Run server directive validation rules
validateConfig(serverRules_, config);
for (const auto &path : config->getLocationPaths())
{
const LocationConfig *locationConfig = config->getLocation(path);
@ -134,7 +173,23 @@ void ValidationEngine::validateServerConfig(const ServerConfig *config)
void ValidationEngine::validateGlobalConfig(const GlobalConfig *config)
{
Log::trace(LOCATION);
// Run global structural validation rules
for (const auto &rule : structuralRules_) {
try {
ValidationResult result = rule->validateGlobal(config);
if (!result.isValidResult()) {
results_.push_back(result);
}
} catch (const std::exception &e) {
results_.push_back(ValidationResult::error(
"Structural rule '" + rule->getRuleName() + "' threw exception: " + e.what()));
}
}
// Run global directive validation rules
validateConfig(globalRules_, config);
for (const auto *serverConfig : config->getServerConfigs())
{
validateServerConfig(serverConfig);
@ -149,6 +204,8 @@ ValidationEngine::ValidationEngine(const GlobalConfig *globalConfig) : globalCon
void ValidationEngine::validate()
{
Log::trace(LOCATION);
results_.clear(); // Clear previous results
if (globalConfig_ != nullptr)
{
validateGlobalConfig(globalConfig_);

View File

@ -6,6 +6,7 @@
#include <webserv/config/ServerConfig.hpp>
#include <webserv/config/validation/ValidationResult.hpp> // for ValidationResult
#include <webserv/config/validation/directive_rules/AValidationRule.hpp> // for AValidationRule
#include <webserv/config/validation/structural_rules/AStructuralValidationRule.hpp> // for AStructuralValidationRule
#include <map> // for map
#include <memory> // for unique_ptr
@ -33,6 +34,9 @@ class ValidationEngine
void addServerRule(const std::string &directiveName, std::unique_ptr<AValidationRule> rule);
void addLocationRule(const std::string &directiveName, std::unique_ptr<AValidationRule> rule);
// Structural validation rules
void addStructuralRule(std::unique_ptr<AStructuralValidationRule> rule);
void validate();
[[nodiscard]] std::vector<ValidationResult> getValidationResults() const;
@ -47,9 +51,11 @@ class ValidationEngine
void validateGlobalConfig(const GlobalConfig *config);
void validateServerConfig(const ServerConfig *config);
void validateLocationConfig(const std::string &path, const LocationConfig *config);
RuleMap globalRules_;
RuleMap serverRules_;
RuleMap locationRules_;
std::vector<std::unique_ptr<AStructuralValidationRule>> structuralRules_;
const GlobalConfig *globalConfig_;
std::vector<ValidationResult> results_;

View File

@ -0,0 +1,58 @@
#pragma once
#include "webserv/config/validation/ValidationResult.hpp"
#include <string>
class GlobalConfig;
class ServerConfig;
class LocationConfig;
class AStructuralValidationRule
{
private:
std::string ruleName_;
std::string description_;
protected:
AStructuralValidationRule(const std::string &ruleName, const std::string &description)
: ruleName_(ruleName), description_(description) {}
public:
virtual ~AStructuralValidationRule() = default;
AStructuralValidationRule(const AStructuralValidationRule &other) = delete;
AStructuralValidationRule &operator=(const AStructuralValidationRule &other) = delete;
AStructuralValidationRule(AStructuralValidationRule &&other) noexcept = delete;
AStructuralValidationRule &operator=(AStructuralValidationRule &&other) noexcept = delete;
// Virtual validation methods - override as needed
[[nodiscard]] virtual ValidationResult validateGlobal(const GlobalConfig *config) const
{
static_cast<void>(config); // Suppress unused parameter warning
return ValidationResult::success(); // Default: no global validation
}
[[nodiscard]] virtual ValidationResult validateServer(const ServerConfig *config) const
{
static_cast<void>(config); // Suppress unused parameter warning
return ValidationResult::success(); // Default: no server validation
}
[[nodiscard]] virtual ValidationResult validateLocation(const LocationConfig *config) const
{
static_cast<void>(config); // Suppress unused parameter warning
return ValidationResult::success(); // Default: no location validation
}
// Non-virtual getters - set in constructor
[[nodiscard]] std::string getRuleName() const
{
return ruleName_;
}
[[nodiscard]] std::string getDescription() const
{
return description_;
}
};

View File

@ -0,0 +1,32 @@
#include "webserv/config/validation/structural_rules/MinimumServerBlocksRule.hpp"
#include "webserv/config/GlobalConfig.hpp"
#include "webserv/log/Log.hpp"
#include <string>
MinimumServerBlocksRule::MinimumServerBlocksRule(size_t minimumServers)
: AStructuralValidationRule("MinimumServerBlocksRule",
"Ensures global config has at least " + std::to_string(minimumServers) + " server block(s)"),
minimumServers_(minimumServers)
{
}
ValidationResult MinimumServerBlocksRule::validateGlobal(const GlobalConfig *config) const
{
Log::trace(LOCATION);
if (config == nullptr) {
return ValidationResult::error("Global config is null");
}
size_t serverCount = config->getServerConfigs().size();
if (serverCount < minimumServers_) {
return ValidationResult::error(
"Global configuration must have at least " + std::to_string(minimumServers_) +
" server block(s), but found " + std::to_string(serverCount));
}
return ValidationResult::success();
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "webserv/config/validation/structural_rules/AStructuralValidationRule.hpp"
#include <cstddef>
class GlobalConfig;
class MinimumServerBlocksRule : public AStructuralValidationRule
{
private:
size_t minimumServers_;
public:
explicit MinimumServerBlocksRule(size_t minimumServers = 1);
~MinimumServerBlocksRule() override = default;
MinimumServerBlocksRule(const MinimumServerBlocksRule &other) = delete;
MinimumServerBlocksRule &operator=(const MinimumServerBlocksRule &other) = delete;
MinimumServerBlocksRule(MinimumServerBlocksRule &&other) noexcept = delete;
MinimumServerBlocksRule &operator=(MinimumServerBlocksRule &&other) noexcept = delete;
[[nodiscard]] ValidationResult validateGlobal(const GlobalConfig *config) const override;
};

View File

@ -0,0 +1,32 @@
#include "webserv/config/validation/structural_rules/RequiredLocationBlocksRule.hpp"
#include "webserv/config/ServerConfig.hpp"
#include "webserv/log/Log.hpp"
#include <string>
RequiredLocationBlocksRule::RequiredLocationBlocksRule(size_t minimumLocations)
: AStructuralValidationRule("RequiredLocationBlocksRule",
"Ensures server has at least " + std::to_string(minimumLocations) + " location block(s)"),
minimumLocations_(minimumLocations)
{
}
ValidationResult RequiredLocationBlocksRule::validateServer(const ServerConfig *config) const
{
Log::trace(LOCATION);
if (config == nullptr) {
return ValidationResult::error("Server config is null");
}
size_t locationCount = config->getLocationPaths().size();
if (locationCount < minimumLocations_) {
return ValidationResult::error(
"Server block must have at least " + std::to_string(minimumLocations_) +
" location block(s), but found " + std::to_string(locationCount));
}
return ValidationResult::success();
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "webserv/config/validation/structural_rules/AStructuralValidationRule.hpp"
#include <cstddef>
class ServerConfig;
class RequiredLocationBlocksRule : public AStructuralValidationRule
{
private:
size_t minimumLocations_;
public:
explicit RequiredLocationBlocksRule(size_t minimumLocations = 1);
~RequiredLocationBlocksRule() override = default;
RequiredLocationBlocksRule(const RequiredLocationBlocksRule &other) = delete;
RequiredLocationBlocksRule &operator=(const RequiredLocationBlocksRule &other) = delete;
RequiredLocationBlocksRule(RequiredLocationBlocksRule &&other) noexcept = delete;
RequiredLocationBlocksRule &operator=(RequiredLocationBlocksRule &&other) noexcept = delete;
[[nodiscard]] ValidationResult validateServer(const ServerConfig *config) const override;
};

View File

@ -0,0 +1,36 @@
#pragma once
// Base class
#include "webserv/config/validation/structural_rules/AStructuralValidationRule.hpp"
// Concrete structural validation rules
#include "webserv/config/validation/structural_rules/MinimumServerBlocksRule.hpp"
#include "webserv/config/validation/structural_rules/RequiredLocationBlocksRule.hpp"
#include "webserv/config/validation/structural_rules/UniqueServerNamesRule.hpp"
/**
* Structural Validation Rules for WebServ Configuration
*
* These rules validate the overall structure and relationships
* between configuration blocks, rather than individual directive values.
*
* Available Rules:
*
* 1. MinimumServerBlocksRule - Ensures global config has minimum number of server blocks
* 2. RequiredLocationBlocksRule - Ensures each server has minimum number of location blocks
* 3. UniqueServerNamesRule - Ensures all server names are unique across configuration
*
* Usage:
*
* // In ValidationEngine setup
* engine.addStructuralRule(std::make_unique<MinimumServerBlocksRule>(1));
* engine.addStructuralRule(std::make_unique<RequiredLocationBlocksRule>(1));
* engine.addStructuralRule(std::make_unique<UniqueServerNamesRule>());
*
* Each rule inherits from AStructuralValidationRule and can validate at:
* - Global level (validateGlobal)
* - Server level (validateServer)
* - Location level (validateLocation)
*
* Rules have descriptive names and descriptions set in their constructors.
*/

View File

@ -0,0 +1,49 @@
#include "webserv/config/validation/structural_rules/UniqueServerNamesRule.hpp"
#include "webserv/config/GlobalConfig.hpp"
#include "webserv/config/ServerConfig.hpp"
#include "webserv/log/Log.hpp"
#include <optional>
#include <set>
#include <string>
UniqueServerNamesRule::UniqueServerNamesRule()
: AStructuralValidationRule("UniqueServerNamesRule",
"Ensures all server blocks have unique server names")
{
}
ValidationResult UniqueServerNamesRule::validateGlobal(const GlobalConfig *config) const
{
Log::trace(LOCATION);
if (config == nullptr) {
return ValidationResult::error("Global config is null");
}
std::set<std::string> serverNames;
auto servers = config->getServerConfigs();
for (const auto *server : servers) {
if (server == nullptr) {
continue;
}
auto serverNameOpt = server->get<std::string>("server_name");
auto listenOpt = server->get<int>("listen");
if (serverNameOpt.has_value() && listenOpt.has_value()) {
const std::string &serverName = serverNameOpt.value();
int listenPort = listenOpt.value();
if (serverNames.contains(serverName + ":" + std::to_string(listenPort))) {
return ValidationResult::error(
"Duplicate server name '" + serverName + "' found in configuration");
}
serverNames.insert(serverName + ":" + std::to_string(listenPort));
}
}
return ValidationResult::success();
}

View File

@ -0,0 +1,20 @@
#pragma once
#include "webserv/config/validation/structural_rules/AStructuralValidationRule.hpp"
class GlobalConfig;
class UniqueServerNamesRule : public AStructuralValidationRule
{
public:
UniqueServerNamesRule();
~UniqueServerNamesRule() override = default;
UniqueServerNamesRule(const UniqueServerNamesRule &other) = delete;
UniqueServerNamesRule &operator=(const UniqueServerNamesRule &other) = delete;
UniqueServerNamesRule(UniqueServerNamesRule &&other) noexcept = delete;
UniqueServerNamesRule &operator=(UniqueServerNamesRule &&other) noexcept = delete;
[[nodiscard]] ValidationResult validateGlobal(const GlobalConfig *config) const override;
};