From 4f89a2918c62e9b9674abdba4cfb310bb05809e6 Mon Sep 17 00:00:00 2001 From: whaffman Date: Mon, 6 Oct 2025 16:00:36 +0200 Subject: [PATCH] feat(structural rules) --- webserv/config/validation/ConfigValidator.cpp | 6 ++ .../config/validation/ValidationEngine.cpp | 59 ++++++++++++++++++- .../config/validation/ValidationEngine.hpp | 6 ++ .../AStructuralValidationRule.hpp | 58 ++++++++++++++++++ .../MinimumServerBlocksRule.cpp | 32 ++++++++++ .../MinimumServerBlocksRule.hpp | 25 ++++++++ .../RequiredLocationBlocksRule.cpp | 32 ++++++++++ .../RequiredLocationBlocksRule.hpp | 25 ++++++++ .../structural_rules/StructuralRules.hpp | 36 +++++++++++ .../UniqueServerNamesRule.cpp | 49 +++++++++++++++ .../UniqueServerNamesRule.hpp | 20 +++++++ 11 files changed, 347 insertions(+), 1 deletion(-) create mode 100644 webserv/config/validation/structural_rules/AStructuralValidationRule.hpp create mode 100644 webserv/config/validation/structural_rules/MinimumServerBlocksRule.cpp create mode 100644 webserv/config/validation/structural_rules/MinimumServerBlocksRule.hpp create mode 100644 webserv/config/validation/structural_rules/RequiredLocationBlocksRule.cpp create mode 100644 webserv/config/validation/structural_rules/RequiredLocationBlocksRule.hpp create mode 100644 webserv/config/validation/structural_rules/StructuralRules.hpp create mode 100644 webserv/config/validation/structural_rules/UniqueServerNamesRule.cpp create mode 100644 webserv/config/validation/structural_rules/UniqueServerNamesRule.hpp diff --git a/webserv/config/validation/ConfigValidator.cpp b/webserv/config/validation/ConfigValidator.cpp index 3471fe9..80af27d 100644 --- a/webserv/config/validation/ConfigValidator.cpp +++ b/webserv/config/validation/ConfigValidator.cpp @@ -4,6 +4,7 @@ #include // for AValidationRule #include // for AllowedValuesRule #include // for PortValidationRule +#include // for structural rules #include // for LOCATION, Log #include // 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(1)); + engine_->addStructuralRule(std::make_unique(1)); + engine_->addStructuralRule(std::make_unique()); + /*Global Directive Rules*/ /*Server Directive Rules*/ diff --git a/webserv/config/validation/ValidationEngine.cpp b/webserv/config/validation/ValidationEngine.cpp index 45145ef..fc2eb87 100644 --- a/webserv/config/validation/ValidationEngine.cpp +++ b/webserv/config/validation/ValidationEngine.cpp @@ -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 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 rule) { @@ -68,7 +76,7 @@ std::vector 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_); diff --git a/webserv/config/validation/ValidationEngine.hpp b/webserv/config/validation/ValidationEngine.hpp index 6b6b000..c9f1645 100644 --- a/webserv/config/validation/ValidationEngine.hpp +++ b/webserv/config/validation/ValidationEngine.hpp @@ -6,6 +6,7 @@ #include #include // for ValidationResult #include // for AValidationRule +#include // for AStructuralValidationRule #include // for map #include // for unique_ptr @@ -33,6 +34,9 @@ class ValidationEngine void addServerRule(const std::string &directiveName, std::unique_ptr rule); void addLocationRule(const std::string &directiveName, std::unique_ptr rule); + // Structural validation rules + void addStructuralRule(std::unique_ptr rule); + void validate(); [[nodiscard]] std::vector 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> structuralRules_; const GlobalConfig *globalConfig_; std::vector results_; diff --git a/webserv/config/validation/structural_rules/AStructuralValidationRule.hpp b/webserv/config/validation/structural_rules/AStructuralValidationRule.hpp new file mode 100644 index 0000000..19a5cee --- /dev/null +++ b/webserv/config/validation/structural_rules/AStructuralValidationRule.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include "webserv/config/validation/ValidationResult.hpp" + +#include + +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(config); // Suppress unused parameter warning + return ValidationResult::success(); // Default: no global validation + } + + [[nodiscard]] virtual ValidationResult validateServer(const ServerConfig *config) const + { + static_cast(config); // Suppress unused parameter warning + return ValidationResult::success(); // Default: no server validation + } + + [[nodiscard]] virtual ValidationResult validateLocation(const LocationConfig *config) const + { + static_cast(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_; + } +}; \ No newline at end of file diff --git a/webserv/config/validation/structural_rules/MinimumServerBlocksRule.cpp b/webserv/config/validation/structural_rules/MinimumServerBlocksRule.cpp new file mode 100644 index 0000000..fb9a23d --- /dev/null +++ b/webserv/config/validation/structural_rules/MinimumServerBlocksRule.cpp @@ -0,0 +1,32 @@ +#include "webserv/config/validation/structural_rules/MinimumServerBlocksRule.hpp" + +#include "webserv/config/GlobalConfig.hpp" +#include "webserv/log/Log.hpp" + +#include + +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(); +} \ No newline at end of file diff --git a/webserv/config/validation/structural_rules/MinimumServerBlocksRule.hpp b/webserv/config/validation/structural_rules/MinimumServerBlocksRule.hpp new file mode 100644 index 0000000..431df8b --- /dev/null +++ b/webserv/config/validation/structural_rules/MinimumServerBlocksRule.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "webserv/config/validation/structural_rules/AStructuralValidationRule.hpp" + +#include + +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; +}; \ No newline at end of file diff --git a/webserv/config/validation/structural_rules/RequiredLocationBlocksRule.cpp b/webserv/config/validation/structural_rules/RequiredLocationBlocksRule.cpp new file mode 100644 index 0000000..1347ac6 --- /dev/null +++ b/webserv/config/validation/structural_rules/RequiredLocationBlocksRule.cpp @@ -0,0 +1,32 @@ +#include "webserv/config/validation/structural_rules/RequiredLocationBlocksRule.hpp" + +#include "webserv/config/ServerConfig.hpp" +#include "webserv/log/Log.hpp" + +#include + +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(); +} \ No newline at end of file diff --git a/webserv/config/validation/structural_rules/RequiredLocationBlocksRule.hpp b/webserv/config/validation/structural_rules/RequiredLocationBlocksRule.hpp new file mode 100644 index 0000000..47d6203 --- /dev/null +++ b/webserv/config/validation/structural_rules/RequiredLocationBlocksRule.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include "webserv/config/validation/structural_rules/AStructuralValidationRule.hpp" + +#include + +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; +}; \ No newline at end of file diff --git a/webserv/config/validation/structural_rules/StructuralRules.hpp b/webserv/config/validation/structural_rules/StructuralRules.hpp new file mode 100644 index 0000000..d789e71 --- /dev/null +++ b/webserv/config/validation/structural_rules/StructuralRules.hpp @@ -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(1)); + * engine.addStructuralRule(std::make_unique(1)); + * engine.addStructuralRule(std::make_unique()); + * + * 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. + */ \ No newline at end of file diff --git a/webserv/config/validation/structural_rules/UniqueServerNamesRule.cpp b/webserv/config/validation/structural_rules/UniqueServerNamesRule.cpp new file mode 100644 index 0000000..a2f45e6 --- /dev/null +++ b/webserv/config/validation/structural_rules/UniqueServerNamesRule.cpp @@ -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 +#include +#include + +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 serverNames; + auto servers = config->getServerConfigs(); + + for (const auto *server : servers) { + if (server == nullptr) { + continue; + } + + auto serverNameOpt = server->get("server_name"); + auto listenOpt = server->get("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(); +} \ No newline at end of file diff --git a/webserv/config/validation/structural_rules/UniqueServerNamesRule.hpp b/webserv/config/validation/structural_rules/UniqueServerNamesRule.hpp new file mode 100644 index 0000000..1ccf402 --- /dev/null +++ b/webserv/config/validation/structural_rules/UniqueServerNamesRule.hpp @@ -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; +}; \ No newline at end of file