Files
clio/src/migration/impl/MigratorsRegister.hpp
2026-05-13 12:09:06 +01:00

218 lines
6.7 KiB
C++

#pragma once
#include "data/BackendInterface.hpp"
#include "migration/MigratiorStatus.hpp"
#include "migration/impl/Spec.hpp"
#include "util/Assert.hpp"
#include "util/Concepts.hpp"
#include "util/config/ObjectView.hpp"
#include "util/log/Logger.hpp"
#include <algorithm>
#include <array>
#include <iterator>
#include <memory>
#include <optional>
#include <ranges>
#include <string>
#include <string_view>
#include <tuple>
#include <utility>
#include <vector>
namespace migration::impl {
/**
* The concept to check if BackendType is the same as the migrator's required backend type
*/
template <typename BackendType, typename MigratorType>
concept MigrationBackend =
requires { requires std::same_as<typename MigratorType::Backend, BackendType>; };
template <typename Backend, typename... MigratorType>
concept BackendMatchAllMigrators = (MigrationBackend<Backend, MigratorType> && ...);
template <typename T>
concept HasCanBlockClio = requires(T t) {
{ t.kCanBlockClio };
};
/**
*@brief The register of migrators. It will dispatch the migration to the corresponding migrator. It
* also hold the shared pointer of backend, which is used by the migrators.
*
*@tparam Backend The backend type
*@tparam MigratorType The migrator types. It should be a concept of MigratorSpec and not have
* duplicate names.
*/
template <typename Backend, typename... MigratorType>
requires AllMigratorSpec<MigratorType...>
class MigratorsRegister {
static_assert(util::hasNoDuplicateNames<MigratorType...>());
util::Logger log_{"Migration"};
std::shared_ptr<Backend> backend_;
template <typename Migrator>
void
callMigration(std::string const& name, util::config::ObjectView const& config)
{
if (name == Migrator::kName) {
LOG(log_.info()) << "Running migration: " << name;
Migrator::runMigration(backend_, config);
backend_->writeMigratorStatus(
name, MigratorStatus(MigratorStatus::Status::Migrated).toString()
);
LOG(log_.info()) << "Finished migration: " << name;
}
}
template <typename T>
static constexpr std::string_view
getDescriptionIfMatch(std::string_view targetName)
{
return (T::kName == targetName) ? T::kDescription : "";
}
template <typename First, typename... Rest>
static constexpr bool
canBlockClioHelper(std::string_view targetName)
{
if (targetName == First::kName) {
if constexpr (HasCanBlockClio<First>) {
return First::kCanBlockClio;
}
return false;
}
if constexpr (sizeof...(Rest) > 0) {
return canBlockClioHelper<Rest...>(targetName);
}
ASSERT(false, "The migrator name is not found");
std::unreachable();
}
public:
/**
* @brief The backend type which is used by the migrators
*/
using BackendType = Backend;
/**
* @brief Construct a new Migrators Register object
*
* @param backend The backend shared pointer
*/
MigratorsRegister(std::shared_ptr<BackendType> backend) : backend_{std::move(backend)}
{
}
/**
* @brief Run the migration according to the given migrator's name
*
* @param name The migrator's name
* @param config The configuration of the migration
*/
void
runMigrator(std::string const& name, util::config::ObjectView const& config)
requires BackendMatchAllMigrators<BackendType, MigratorType...>
{
(callMigration<MigratorType>(name, config), ...);
}
/**
* @brief Get the status of all the migrators
*
* @return A vector of tuple, the first element is the migrator's name, the second element is
* the status of the migrator
*/
[[nodiscard]] std::vector<std::tuple<std::string, MigratorStatus>>
getMigratorsStatus() const
{
auto const fullList = getMigratorNames();
std::vector<std::tuple<std::string, MigratorStatus>> status;
std::ranges::transform(fullList, std::back_inserter(status), [&](auto const& migratorName) {
auto const migratorNameStr = std::string(migratorName);
return std::make_tuple(migratorNameStr, getMigratorStatus(migratorNameStr));
});
return status;
}
/**
* @brief Get the status of a migrator by its name
*
* @param name The migrator's name to get the status
* @return The status of the migrator
*/
[[nodiscard]] MigratorStatus
getMigratorStatus(std::string const& name) const
{
auto const fullList = getMigratorNames();
if (std::ranges::find(fullList, name) == fullList.end()) {
return MigratorStatus::Status::NotKnown;
}
auto const statusStringOpt = data::synchronous([&](auto yield) {
return backend_->fetchMigratorStatus(name, yield);
});
return statusStringOpt ? MigratorStatus::fromString(statusStringOpt.value())
: MigratorStatus::Status::NotMigrated;
}
/**
* @brief Get all registered migrators' names
*
* @return A array of migrator's names
*/
[[nodiscard]] constexpr auto
getMigratorNames() const
{
return std::array<std::string_view, sizeof...(MigratorType)>{MigratorType::kName...};
}
/**
* @brief Get the description of a migrator by its name
*
* @param name The migrator's name
* @return The description of the migrator
*/
[[nodiscard]] std::string
getMigratorDescription(std::string const& name) const
{
if constexpr (sizeof...(MigratorType) == 0) {
return "No Description";
} else {
// Fold expression to search through all types
std::string const result = ([](std::string const& name) {
return std::string(getDescriptionIfMatch<MigratorType>(name));
}(name) + ...);
return result.empty() ? "No Description" : result;
}
}
/**
* @brief Return if the given migrator can block Clio server
*
* @param name The migrator's name
* @return std::nullopt if the migrator name is not found, or a boolean value indicating whether
* the migrator is blocking Clio server.
*/
[[nodiscard]] std::optional<bool>
canMigratorBlockClio(std::string_view name) const
{
if constexpr (sizeof...(MigratorType) == 0) {
return std::nullopt;
} else {
auto const migratiors = getMigratorNames();
if (std::ranges::find(migratiors, name) == migratiors.end())
return std::nullopt;
return canBlockClioHelper<MigratorType...>(name);
}
}
};
} // namespace migration::impl