Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 53 additions & 2 deletions src/config_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,16 @@ void ConfigManager::parseTemplateConfig() {
if (template_node) {
template_config.path = template_node["path"].as<std::string>();
std::filesystem::path template_path_relative(template_config.path);
template_config.path = std::filesystem::absolute((base_path / template_path_relative).lexically_normal()).string();
std::filesystem::path joined = (base_path / template_path_relative).lexically_normal();
// In bundled mode the template path is a bundle-relative key
// (e.g. "sqls"), not a filesystem path. Calling absolute() would
// turn it into "/cwd/sqls" which the EmbeddedArchiveFileProvider
// would fail to look up.
if (FileProviderFactory::GetBundleContents() != nullptr) {
template_config.path = joined.string();
} else {
template_config.path = std::filesystem::absolute(joined).string();
}
CROW_LOG_DEBUG << "Template Path: " << template_config.path;

if (template_node["environment-whitelist"]) {
Expand Down Expand Up @@ -430,7 +439,40 @@ void ConfigManager::loadEndpointConfigsRecursively(const std::filesystem::path&
size_t total_yaml_files = 0;
size_t loaded_endpoints = 0;

if (std::filesystem::exists(template_path) && std::filesystem::is_directory(template_path)) {
// Bundled mode: enumerate endpoint YAMLs by prefix-matching the
// in-memory archive. EmbeddedArchiveFileProvider::ListFiles is
// non-recursive on purpose, so we walk the bundle directly here.
if (auto bundle = FileProviderFactory::GetBundleContents();
bundle != nullptr) {
std::string prefix = template_path.string();
// Normalise to a key shape that matches archive entries (no
// leading "./", no leading "/", a single trailing "/").
while (prefix.size() >= 2 && prefix[0] == '.' && prefix[1] == '/') {
prefix.erase(0, 2);
}
if (!prefix.empty() && prefix.front() == '/') {
prefix.erase(0, 1);
}
if (!prefix.empty() && prefix.back() != '/') {
prefix += '/';
}
for (const auto& [name, _data] : *bundle) {
if (!prefix.empty() && name.rfind(prefix, 0) != 0) {
continue;
}
const std::filesystem::path entry_path(name);
const auto extension = entry_path.extension();
if (extension != ".yaml" && extension != ".yml") {
continue;
}
total_yaml_files++;
size_t endpoints_before = endpoints.size();
loadEndpointConfig(name);
if (endpoints.size() > endpoints_before) {
loaded_endpoints++;
}
}
} else if (std::filesystem::exists(template_path) && std::filesystem::is_directory(template_path)) {
for (const auto& entry : std::filesystem::recursive_directory_iterator(template_path)) {
if (entry.is_regular_file()) {
auto extension = entry.path().extension();
Expand Down Expand Up @@ -1198,6 +1240,15 @@ std::shared_ptr<IFileProvider> ConfigManager::getFileProvider() const {
return std::make_shared<CachingFileProvider>(base_provider, cache_config);
}

// Bundled mode (#62): templates live in the in-memory archive, not on
// disk. Route through the factory so SQLTemplateProcessor gets an
// EmbeddedArchiveFileProvider when SetBundleContents has been called.
// Falls back to LocalFileProvider when no bundle is active, which
// matches the pre-bundled behaviour.
if (FileProviderFactory::GetBundleContents() != nullptr) {
return FileProviderFactory::CreateProvider(template_config.path);
}

return config_loader->getFileProvider();
}
std::string ConfigManager::getCacheSchema() const { return cache_schema; }
Expand Down
57 changes: 38 additions & 19 deletions src/extended_yaml_parser.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "include/extended_yaml_parser.hpp"
#include "include/vfs_adapter.hpp"
#include <fstream>
#include <sstream>
#include <iostream>
Expand All @@ -15,6 +16,34 @@

namespace flapi {

namespace {

// Read a file through FileProviderFactory so bundled-mode startup
// (`flapi pack` then run from a clean cwd) can resolve `flapi.yaml`
// and any `{{include from ...}}` references against the in-memory
// archive instead of std::ifstream. When no bundle is active the
// factory returns a LocalFileProvider, so the unbundled path is
// behaviourally unchanged.
std::string ReadConfigFile(const std::filesystem::path& file_path) {
const std::string path_str = file_path.string();
auto provider = FileProviderFactory::CreateProvider(path_str);
if (!provider->FileExists(path_str)) {
throw std::runtime_error("Could not open file: " + path_str);
}
return provider->ReadFile(path_str);
}

// Provider-aware existence check used by include resolution. When a bundle
// is active, std::filesystem::exists would always say "no" for entries
// that live only in the in-memory archive.
bool ConfigFileExists(const std::filesystem::path& file_path) {
const std::string path_str = file_path.string();
auto provider = FileProviderFactory::CreateProvider(path_str);
return provider->FileExists(path_str);
}

} // namespace

// IncludeConfig implementation
bool ExtendedYamlParser::IncludeConfig::isEnvironmentVariableAllowed(const std::string& var_name) const {
if (environment_whitelist.empty()) {
Expand Down Expand Up @@ -50,18 +79,15 @@ ExtendedYamlParser::ParseResult ExtendedYamlParser::parseFile(const std::filesys
actual_base_path = file_path.parent_path();
}

// Load file content
std::ifstream file(file_path);
if (!file.is_open()) {
std::string content;
try {
content = ReadConfigFile(file_path);
} catch (const std::exception& e) {
result.success = false;
result.error_message = "Could not open file: " + file_path.string();
result.error_message = e.what();
return result;
}

std::stringstream buffer;
buffer << file.rdbuf();
std::string content = buffer.str();

// Track included files for circular dependency detection
std::unordered_set<std::string> included_files;
included_files.insert(std::filesystem::absolute(file_path).string());
Expand Down Expand Up @@ -573,20 +599,20 @@ bool ExtendedYamlParser::resolveIncludePath(const std::filesystem::path& include
const std::vector<std::string>& include_paths) {
// First try relative to base path
resolved_path = base_path / include_path;
if (std::filesystem::exists(resolved_path)) {
if (ConfigFileExists(resolved_path)) {
return true;
}

// Try absolute path
if (include_path.is_absolute() && std::filesystem::exists(include_path)) {
if (include_path.is_absolute() && ConfigFileExists(include_path)) {
resolved_path = include_path;
return true;
}

// Try include paths
for (const auto& include_base : include_paths) {
resolved_path = std::filesystem::path(include_base) / include_path;
if (std::filesystem::exists(resolved_path)) {
if (ConfigFileExists(resolved_path)) {
return true;
}
}
Expand All @@ -595,14 +621,7 @@ bool ExtendedYamlParser::resolveIncludePath(const std::filesystem::path& include
}

YAML::Node ExtendedYamlParser::loadYamlFile(const std::filesystem::path& file_path) {
std::ifstream file(file_path);
if (!file.is_open()) {
throw std::runtime_error("Could not open file: " + file_path.string());
}

std::stringstream buffer;
buffer << file.rdbuf();
return YAML::Load(buffer.str());
return YAML::Load(ReadConfigFile(file_path));
}

YAML::Node ExtendedYamlParser::extractSection(const YAML::Node& node, const std::string& section_name) {
Expand Down
17 changes: 17 additions & 0 deletions src/sql_template_processor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,23 @@ std::string SQLTemplateProcessor::getFullTemplatePath(const std::string& templat
return basePath + templateSource;
}

// In bundled mode the endpoint parser already resolved templateSource
// against the YAML's parent directory, so it carries the full bundle
// key (e.g. "sqls/hello.sql"). Joining with the basePath ("sqls")
// would produce "sqls/sqls/hello.sql". Mirror the absolute-path
// short-circuit above: if templateSource already starts with the
// basePath prefix, return it as-is.
{
std::string base_with_sep = basePath;
if (!base_with_sep.empty() && base_with_sep.back() != '/') {
base_with_sep += '/';
}
if (!base_with_sep.empty() &&
templateSource.compare(0, base_with_sep.size(), base_with_sep) == 0) {
return templateSource;
}
}

// Local path resolution
std::filesystem::path fullPath = std::filesystem::path(basePath) / templateSource;
return fullPath.string();
Expand Down
Loading
Loading