feat(directives): implement directive parsing and factory pattern for configuration

This commit is contained in:
Quinten 2025-09-25 17:33:14 +02:00
parent a54cdaa841
commit c9e74ca508
20 changed files with 530 additions and 20 deletions

View File

@ -1,3 +1,5 @@
autoindex on
server {
listen 8080;
host 0.0.0.0;
@ -39,7 +41,6 @@ server {
cgi_pass /cgi-bin/;
cgi_ext .py .php;
}
asdfasdfasdf
server {
listen 8081;
host 127.0.0.1;

40
docs/directives.md Normal file
View File

@ -0,0 +1,40 @@
## Directives
- listen INT {server}
- host STRING {server}
- server_name STRING {server}
- root STRING {server, location}
- index STRING[] {server, location}
- error_page INT STIRNG {server, location}
- client_max_body_size SIZE {server, location}
- autoindex BOOL {location}
- allowed_methods STRING[] {location}
- cgi_pass STRING {location}
- cgi_ext STRING[] {location}
- cgi_timout INT {location}
- upload_enabled BOOL {location}
- upload_store STRING {location}
- redirect INT STRING {location}
struct Directives
{
}
LocationConfig lcocation;
location["index"]
Decl operator[](std::string const & key) -> ConfigValue &;
{
declaration_map_t::iterator it = declarations.find(key);
if (it == declarations.end())
serverconfig[key] = Declaration(key);
}
IntDecl get()
StringDecl
BoolDecl
SizeDecl
StringArrayDecl
auto decl = location[root].get();

View File

View File

@ -1,12 +1,14 @@
#include <webserv/config/ConfigManager.hpp>
#include <webserv/config/ServerConfig.hpp> // for ServerConfig
#include <webserv/config/utils.hpp> // for trim, findCorrespondingClosingBrace, trimSemi
#include <webserv/log/Log.hpp> // for Log
#include <webserv/config/ServerConfig.hpp> // for ServerConfig
#include <webserv/config/directive/DirectiveFactory.hpp> // for DirectiveFactory
#include <webserv/config/utils.hpp> // for trim, findCorrespondingClosingBrace, trimSemi
#include <webserv/log/Log.hpp> // for Log
#include <cstddef> // for size_t
#include <fstream> // for basic_ifstream, basic_istream, basic_filebuf, basic_ostream::operator<<, ifstream, istringstream, stringstream
#include <sstream> // for basic_stringstream, basic_istringstream
#include <stdexcept> // for runtime_error
#include <string>
ConfigManager::ConfigManager() : initialized_(false) {}
@ -82,7 +84,7 @@ void ConfigManager::parseConfigFile(const std::string &filePath)
removeComments(content);
std::string globalDeclarations;
Log::trace("Content before parsing servers:\n" + content);
size_t pos = 0;
while (true)
{
@ -96,6 +98,9 @@ void ConfigManager::parseConfigFile(const std::string &filePath)
}
// Add global declarations before this server block
globalDeclarations += content.substr(pos, serverPos - pos);
Log::trace(LOCATION, {{"pos", std::to_string(pos)},
{"serverPos", std::to_string(serverPos)},
{"globalDeclarations", globalDeclarations}});
size_t closeBrace = utils::findCorrespondingClosingBrace(content, bracePos);
if (closeBrace == std::string::npos)
{
@ -107,14 +112,25 @@ void ConfigManager::parseConfigFile(const std::string &filePath)
pos = closeBrace + 1;
}
// parseGlobalDeclarations(globalDeclarations); // Implement this function to handle global config
Log::info("Global Declarations...");
parseGlobalDeclarations(globalDeclarations); // Implement this function to handle global config
file.close();
}
// void ConfigManager::parseGlobalDeclarations(const std::string &declarations)
// {
// // Placeholder for actual global declarations parsing logic
// std::cout << "Parsing global declarations:\n" << declarations << '\n';
// // Implement the parsing logic here
// }
void ConfigManager::parseGlobalDeclarations(const std::string &declarations)
{
Log::trace(LOCATION);
std::stringstream ss(declarations);
std::string line;
while (ss.good())
{
std::getline(ss, line);
line = utils::trim(line);
if (line.empty())
{
continue;
}
Log::info("Global Declaration: " + line);
auto directive = DirectiveFactory::createDirective(line);
globalDirectives_.push_back(std::move(directive));
}
}

View File

@ -1,7 +1,9 @@
#pragma once
#include "webserv/config/directive/ADirective.hpp"
#include <webserv/config/ServerConfig.hpp>
#include <memory>
#include <string>
#include <vector>
@ -23,7 +25,8 @@ class ConfigManager
ConfigManager();
~ConfigManager();
std::vector<ServerConfig> serverConfigs_;
std::vector<std::unique_ptr<ADirective>> globalDirectives_;
void parseConfigFile(const std::string &filePath);
// void parseGlobalDeclarations(const std::string &declarations);
void parseGlobalDeclarations(const std::string &declarations);
};

View File

@ -0,0 +1,16 @@
#include <webserv/config/directive/ADirective.hpp> // for ADirective
DirectiveValue ADirective::get() const
{
return {getValue()};
}
std::string ADirective::getName() const
{
return name_;
}
void ADirective::setName(const std::string &name)
{
name_ = name;
}

View File

@ -0,0 +1,62 @@
#pragma once
#include <any>
#include <array>
#include <string>
class DirectiveValue
{
public:
DirectiveValue(std::any value) : value_(value) {}
template <typename T> operator T() const { return std::any_cast<T>(value_); }
private:
std::any value_;
};
class ADirective
{
public:
ADirective() = delete;
ADirective(std::string name) : name_(std::move(name)) {}
ADirective(const ADirective &other) = delete;
ADirective &operator=(const ADirective &other) = delete;
ADirective(ADirective &&other) noexcept = delete;
ADirective &operator=(ADirective &&other) noexcept = delete;
virtual ~ADirective() {}
virtual void parse(const std::string &value) = 0;
[[nodiscard]] virtual std::any getValue() const = 0;
[[nodiscard]] DirectiveValue get() const;
[[nodiscard]] std::string getName() const;
void setName(const std::string &name);
protected:
std::string name_;
};
// Supported directives:
// - listen INT {server}
// - host STRING {server}
// - server_name STRING {server}
// - root STRING {server, location}
// - index STRING[] {server, location}
// - error_page INT STIRNG {server, location}
// - client_max_body_size SIZE {server, location}
// - autoindex BOOL {location}
// - allowed_methods STRING[] {location}
// - cgi_pass STRING {location}
// - cgi_ext STRING[] {location}
// - cgi_timout INT {location}
// - upload_enabled BOOL {location}
// - upload_store STRING {location}
// - redirect INT STRING {location}

View File

@ -0,0 +1,30 @@
#include <webserv/config/directive/BoolDirective.hpp> // for IntDirective
#include <webserv/config/utils.hpp> // for trim
#include <algorithm>
#include <any>
#include <stdexcept>
void BoolDirective::parse(const std::string &arg)
{
std::string value = arg;
value = utils::trim(value);
std::ranges::transform(value, value.begin(), ::tolower);
if (value == "true" || value == "1" || value == "on" || value == "yes")
{
value_ = true;
}
else if (value == "false" || value == "0" || value == "off" || value == "no")
{
value_ = false;
}
else
{
throw std::invalid_argument("Invalid boolean value: " + value);
}
}
std::any BoolDirective::getValue() const
{
return value_;
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "ADirective.hpp"
#include <any>
class BoolDirective : public ADirective
{
public:
BoolDirective() = delete;
BoolDirective(const std::string &name, const std::string &value) : ADirective(name) { parse(value); }
BoolDirective(const BoolDirective &other) = delete;
BoolDirective &operator=(const BoolDirective &other) = delete;
BoolDirective(BoolDirective &&other) noexcept = delete;
BoolDirective &operator=(BoolDirective &&other) noexcept = delete;
~BoolDirective() override = default;
void parse(const std::string &value) override;
[[nodiscard]] std::any getValue() const override;
private:
bool value_ = false;
};

View File

@ -0,0 +1,64 @@
#include <webserv/config/directive/ADirective.hpp>
#include <webserv/config/directive/BoolDirective.hpp>
#include <webserv/config/directive/DirectiveFactory.hpp> // for DirectiveFactory
#include <webserv/config/directive/IntDirective.hpp>
#include <webserv/config/directive/SizeDirective.hpp>
#include <webserv/config/directive/StringDirective.hpp>
#include <webserv/config/directive/VectorDirective.hpp>
#include <webserv/config/utils.hpp> // for trim, findCorrespondingClosingBrace, trimSemi
#include <webserv/log/Log.hpp> // for Log
std::unique_ptr<ADirective> DirectiveFactory::createDirective(const std::string &line)
{
std::stringstream ss(line);
std::string name;
ss >> name;
std::string arg;
std::getline(ss, arg);
std::string_view type;
for (const auto &directive : supportedDirectives)
{
if (directive.name == name)
{
type = directive.type;
break;
}
}
if (type.empty())
{
throw std::invalid_argument("Unsupported directive: " + name);
}
return create(type, name, arg);
}
std::unique_ptr<ADirective> DirectiveFactory::create(std::string_view type, const std::string &name,
const std::string &arg)
{
const auto &factories = getFactories();
auto it = factories.find(type);
if (it == factories.end())
{
throw std::invalid_argument("No factory found for directive type: " + std::string(type));
}
return it->second(name, utils::trimSemi(utils::trim(arg)));
}
const std::unordered_map<std::string_view, DirectiveFactory::CreatorFunc> &DirectiveFactory::getFactories()
{
static const std::unordered_map<std::string_view, CreatorFunc> factories = {
{"BoolDirective",
[](const std::string &name, const std::string &arg) { return std::make_unique<BoolDirective>(name, arg); }},
{"IntDirective",
[](const std::string &name, const std::string &arg) { return std::make_unique<IntDirective>(name, arg); }},
{"SizeDirective",
[](const std::string &name, const std::string &arg) { return std::make_unique<SizeDirective>(name, arg); }},
{"StringDirective",
[](const std::string &name, const std::string &arg) { return std::make_unique<StringDirective>(name, arg); }},
{"VectorDirective",
[](const std::string &name, const std::string &arg) { return std::make_unique<VectorDirective>(name, arg); }},
};
return factories;
}

View File

@ -0,0 +1,43 @@
#include "webserv/config/directive/ADirective.hpp"
#include <functional>
#include <memory>
#include <string_view>
#include <unordered_map>
class DirectiveFactory
{
public:
static std::unique_ptr<ADirective> createDirective(const std::string &line);
private:
using CreatorFunc = std::function<std::unique_ptr<ADirective>(const std::string &, const std::string &arg)>;
static const std::unordered_map<std::string_view, CreatorFunc> &getFactories();
static std::unique_ptr<ADirective> create(std::string_view type, const std::string &name, const std::string &arg);
struct DirectiveInfo
{
std::string_view name;
std::string_view type;
std::string_view context;
};
constexpr static std::array<DirectiveInfo, 15> supportedDirectives = {{
{.name = "listen", .type = "IntDirective", .context = "SL"},
{.name = "host", .type = "StringDirective", .context = "SL"},
{.name = "server_name", .type = "VectorDirective", .context = "SL"},
{.name = "root", .type = "StringDirective", .context = "SL"},
{.name = "index", .type = "VectorDirective", .context = "SL"},
{.name = "error_page", .type = "VectorDirective", .context = "SL"},
{.name = "client_max_body_size", .type = "SizeDirective", .context = "SL"},
{.name = "autoindex", .type = "BoolDirective", .context = "L"},
{.name = "allowed_methods", .type = "VectorDirective", .context = "L"},
{.name = "cgi_pass", .type = "StringDirective", .context = "L"},
{.name = "cgi_ext", .type = "VectorDirective", .context = "L"},
{.name = "cgi_timeout", .type = "IntDirective", .context = "L"},
{.name = "upload_enabled", .type = "BoolDirective", .context = "L"},
{.name = "upload_store", .type = "StringDirective", .context = "L"},
{.name = "redirect", .type = "VectorDirective", .context = "L"},
}};
};

View File

@ -0,0 +1,13 @@
#include <webserv/config/directive/IntDirective.hpp> // for IntDirective
#include <any>
void IntDirective::parse(const std::string &value)
{
value_ = std::stoi(value); // TODO: check parsing
}
std::any IntDirective::getValue() const
{
return value_;
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "ADirective.hpp"
#include <any>
class IntDirective : public ADirective
{
public:
IntDirective() = delete;
IntDirective(const std::string &name, const std::string &value) : ADirective(name) { parse(value); }
IntDirective(const IntDirective &other) = delete;
IntDirective &operator=(const IntDirective &other) = delete;
IntDirective(IntDirective &&other) noexcept = delete;
IntDirective &operator=(IntDirective &&other) noexcept = delete;
~IntDirective() override = default;
void parse(const std::string &value) override;
[[nodiscard]] std::any getValue() const override;
private:
int value_ = 0;
};

View File

@ -0,0 +1,49 @@
#include <webserv/config/directive/SizeDirective.hpp> // for SizeDirective
#include <webserv/config/utils.hpp> // for trim
#include <algorithm>
#include <any>
#include <cctype>
#include <stdexcept>
void SizeDirective::parse(const std::string &value)
{
size_t multiplier = 1;
size_t idx = 0;
std::string number = value;
std::ranges::transform(number, number.begin(), ::tolower);
number = utils::trim(number);
if (number.find_first_not_of("01234567890kmg") != std::string::npos)
{
throw std::invalid_argument("Invalid size directive: " + value);
}
value_ = std::stoul(number, &idx);
if (idx == number.size())
{
return;
}
std::string suffix = number.substr(idx);
if (suffix == "k")
{
multiplier = 1024;
}
else if (suffix == "m")
{
multiplier = 1024 * 1024;
}
else if (suffix == "g")
{
multiplier = 1024 * 1024 * 1024;
}
else
{
throw std::invalid_argument("Invalid size directive: " + value);
}
value_ *= multiplier;
}
std::any SizeDirective::getValue() const
{
return value_;
}

View File

@ -0,0 +1,27 @@
#pragma once
#include "ADirective.hpp"
#include <any>
class SizeDirective : public ADirective
{
public:
SizeDirective() = delete;
SizeDirective(const std::string &name, const std::string &value) : ADirective(name) { parse(value); }
SizeDirective(const SizeDirective &other) = delete;
SizeDirective &operator=(const SizeDirective &other) = delete;
SizeDirective(SizeDirective &&other) noexcept = delete;
SizeDirective &operator=(SizeDirective &&other) noexcept = delete;
~SizeDirective() override = default;
void parse(const std::string &value) override;
[[nodiscard]] std::any getValue() const override;
private:
size_t value_ = 0;
};

View File

@ -0,0 +1,13 @@
#include <webserv/config/directive/StringDirective.hpp> // for IntDirective
#include <any>
void StringDirective::parse(const std::string &value)
{
value_ = value;
}
std::any StringDirective::getValue() const
{
return value_;
}

View File

@ -0,0 +1,25 @@
#pragma once
#include "ADirective.hpp"
class StringDirective : public ADirective
{
public:
StringDirective() = delete;
StringDirective(const std::string &name, const std::string &value) : ADirective(name) { parse(value); }
StringDirective(const StringDirective &other) = delete;
StringDirective &operator=(const StringDirective &other) = delete;
StringDirective(StringDirective &&other) noexcept = delete;
StringDirective &operator=(StringDirective &&other) noexcept = delete;
~StringDirective() override = default;
void parse(const std::string &value) override;
[[nodiscard]] std::any getValue() const override;
private:
std::string value_;
};

View File

@ -0,0 +1,23 @@
#include <webserv/config/directive/VectorDirective.hpp> // for IntDirective
#include <any>
#include <sstream> // for std::getline, std::basic_istream, std::char_traits, std::basic_stringbuf
void VectorDirective::parse(const std::string &value)
{
std::stringstream ss(value);
while(ss.good())
{
std::string item;
std::getline(ss, item, ' ');// index indx.html
if (!item.empty())
{
value_.push_back(item);
}
}
}
std::any VectorDirective::getValue() const
{
return value_;
}

View File

@ -0,0 +1,28 @@
#pragma once
#include "ADirective.hpp"
#include <string>
#include <vector>
class VectorDirective : public ADirective
{
public:
VectorDirective() = delete;
VectorDirective(const std::string &name, const std::string &value) : ADirective(name) { parse(value); }
VectorDirective(const VectorDirective &other) = delete;
VectorDirective &operator=(const VectorDirective &other) = delete;
VectorDirective(VectorDirective &&other) noexcept = delete;
VectorDirective &operator=(VectorDirective &&other) noexcept = delete;
~VectorDirective() override = default;
void parse(const std::string &value) override;
[[nodiscard]] std::any getValue() const override;
private:
std::vector<std::string> value_;
};

View File

@ -1,11 +1,14 @@
#include <webserv/config/ConfigManager.hpp> // for ConfigManager
#include <webserv/log/Log.hpp> // for Log, LOCATION
#include <webserv/server/Server.hpp> // for Server
#include <webserv/config/ConfigManager.hpp> // for ConfigManager
#include <webserv/config/directive/ADirective.hpp> // for ADirective
#include <webserv/config/directive/IntDirective.hpp> // for IntDirective
#include <webserv/log/Log.hpp> // for Log, LOCATION
#include <webserv/server/Server.hpp> // for Server
#include <iostream> // for basic_ostream, operator<<, cerr, ios_base
#include <map> // for map
#include <string> // for basic_string, char_traits, allocator, operator+, operator<=>
#include <utility> // for pair
#include <numeric>
#include <string> // for basic_string, char_traits, allocator, operator+, operator<=>
#include <utility> // for pair
int main(int argc, char **argv)
{
@ -21,7 +24,7 @@ int main(int argc, char **argv)
Log::warning("Testing context: " + LOCATION, {{"key1", "value1"}, {"key2", "value2"}});
ConfigManager::getInstance().init(argv[1]); // NOLINT
Server server(ConfigManager::getInstance());
server.start();
return 0;
}