| @@ -16,6 +16,7 @@ | |||
| "neo/io", | |||
| "neo/http", | |||
| "neo/url", | |||
| "boost/leaf", | |||
| // Explicit zlib link is required due to linker input order bug. | |||
| // Can be removed after alpha.5 | |||
| "zlib/zlib", | |||
| @@ -20,6 +20,7 @@ | |||
| "fmt^7.0.3", | |||
| "neo-http^0.1.0", | |||
| "neo-io^0.1.0", | |||
| "boost.leaf~0.3.0", | |||
| ], | |||
| "test_driver": "Catch-Main" | |||
| } | |||
| @@ -4,14 +4,22 @@ | |||
| #include <dds/dym.hpp> | |||
| #include <dds/error/errors.hpp> | |||
| #include <dds/repo/repo.hpp> | |||
| #include <dds/repoman/repoman.hpp> | |||
| #include <dds/source/dist.hpp> | |||
| #include <dds/toolchain/from_json.hpp> | |||
| #include <dds/util/fs.hpp> | |||
| #include <dds/util/log.hpp> | |||
| #include <dds/util/paths.hpp> | |||
| #include <dds/util/result.hpp> | |||
| #include <dds/util/signal.hpp> | |||
| #include <boost/leaf/handle_error.hpp> | |||
| #include <boost/leaf/handle_exception.hpp> | |||
| #include <fmt/ostream.h> | |||
| #include <json5/parse_data.hpp> | |||
| #include <neo/assert.hpp> | |||
| #include <neo/sqlite3/error.hpp> | |||
| #include <nlohmann/json.hpp> | |||
| #include <range/v3/action/join.hpp> | |||
| #include <range/v3/range/conversion.hpp> | |||
| #include <range/v3/view/concat.hpp> | |||
| @@ -396,6 +404,199 @@ struct cli_catalog { | |||
| } | |||
| }; | |||
| /* | |||
| ######## ######## ######## ####### ## ## ### ## ## | |||
| ## ## ## ## ## ## ## ### ### ## ## ### ## | |||
| ## ## ## ## ## ## ## #### #### ## ## #### ## | |||
| ######## ###### ######## ## ## ## ### ## ## ## ## ## ## | |||
| ## ## ## ## ## ## ## ## ######### ## #### | |||
| ## ## ## ## ## ## ## ## ## ## ## ### | |||
| ## ## ######## ## ####### ## ## ## ## ## ## | |||
| */ | |||
| struct cli_repoman { | |||
| cli_base& base; | |||
| args::Command cmd{base.cmd_group, "repoman", "Manage a package package repository"}; | |||
| common_flags _common{cmd}; | |||
| args::Group repoman_group{cmd, "Repoman subcommand"}; | |||
| struct { | |||
| cli_repoman& parent; | |||
| args::Command cmd{parent.repoman_group, "init", "Initialize a new repository directory"}; | |||
| common_flags _common{cmd}; | |||
| args::Positional<dds::fs::path> where{cmd, | |||
| "<repo-path>", | |||
| "Directory where the repository will be created", | |||
| args::Options::Required}; | |||
| string_flag name{cmd, | |||
| "<name>", | |||
| "Give the repository a name (should be GLOBALLY unique). If not provided, " | |||
| "a new random one will be generated.", | |||
| {"name"}}; | |||
| int run() { | |||
| auto repo | |||
| = dds::repo_manager::create(where.Get(), | |||
| name ? std::make_optional(name.Get()) : std::nullopt); | |||
| dds_log(info, "Created new repository '{}' in {}", repo.root(), repo.name()); | |||
| return 0; | |||
| } | |||
| } init{*this}; | |||
| struct { | |||
| cli_repoman& parent; | |||
| args::Command cmd{parent.repoman_group, "import", "Import packages into a repository"}; | |||
| common_flags _common{cmd}; | |||
| args::Positional<dds::fs::path> where{cmd, | |||
| "<repo-path>", | |||
| "Directory of the repository to import", | |||
| args::Options::Required}; | |||
| args::PositionalList<dds::fs::path> files{cmd, | |||
| "<targz-path>", | |||
| "Path to one or more sdist archives to import"}; | |||
| int run() { | |||
| auto repo = dds::repo_manager::open(where.Get()); | |||
| for (auto pkg : files.Get()) { | |||
| repo.import_targz(pkg); | |||
| } | |||
| return 0; | |||
| } | |||
| } import{*this}; | |||
| struct { | |||
| cli_repoman& parent; | |||
| args::Command cmd{parent.repoman_group, "remove", "Remove packages from the repository"}; | |||
| common_flags _common{cmd}; | |||
| args::Positional<dds::fs::path> where{cmd, | |||
| "<repo-path>", | |||
| "Directory of the repository to import", | |||
| args::Options::Required}; | |||
| args::PositionalList<std::string> packages{cmd, | |||
| "<package-id>", | |||
| "One or more identifiers of packages to remove"}; | |||
| int run() { | |||
| auto repo = dds::repo_manager::open(where.Get()); | |||
| for (auto& str : packages) { | |||
| auto pkg_id = dds::package_id::parse(str); | |||
| repo.delete_package(pkg_id); | |||
| } | |||
| return 0; | |||
| } | |||
| } remove{*this}; | |||
| struct { | |||
| cli_repoman& parent; | |||
| args::Command cmd{parent.repoman_group, "ls", "List packages in the repository"}; | |||
| common_flags _common{cmd}; | |||
| args::Positional<dds::fs::path> where{cmd, | |||
| "<repo-path>", | |||
| "Directory of the repository to inspect", | |||
| args::Options::Required}; | |||
| int run() { | |||
| auto repo = dds::repo_manager::open(where.Get()); | |||
| for (auto pkg_id : repo.all_packages()) { | |||
| std::cout << pkg_id.to_string() << '\n'; | |||
| } | |||
| return 0; | |||
| } | |||
| } ls{*this}; | |||
| dds::result<int> _run() { | |||
| if (init.cmd) { | |||
| return init.run(); | |||
| } else if (import.cmd) { | |||
| return import.run(); | |||
| } else if (remove.cmd) { | |||
| return remove.run(); | |||
| } else if (ls.cmd) { | |||
| return ls.run(); | |||
| } | |||
| return 66; | |||
| } | |||
| int run() { | |||
| return boost::leaf::try_handle_all( // | |||
| [&]() -> dds::result<int> { | |||
| try { | |||
| return _run(); | |||
| } catch (...) { | |||
| return dds::capture_exception(); | |||
| } | |||
| }, | |||
| [](dds::e_sqlite3_error_exc, | |||
| boost::leaf::match<neo::sqlite3::errc, neo::sqlite3::errc::constraint_unique>, | |||
| dds::e_repo_import_targz tgz, | |||
| dds::package_id pkg_id) { | |||
| dds_log(error, | |||
| "Package {} (from {}) is already present in the repository", | |||
| pkg_id.to_string(), | |||
| tgz.path); | |||
| return 1; | |||
| }, | |||
| [](dds::e_sqlite3_error_exc e, dds::e_repo_import_targz tgz) { | |||
| dds_log(error, | |||
| "Database error while importing tar file {}: {}", | |||
| tgz.path, | |||
| e.message); | |||
| return 1; | |||
| }, | |||
| [](dds::e_sqlite3_error_exc e, dds::e_init_repo init, dds::e_init_repo_db init_db) { | |||
| dds_log( | |||
| error, | |||
| "SQLite error while initializing repository in [{}] (SQlite database {}): {}", | |||
| init.path, | |||
| init_db.path, | |||
| e.message); | |||
| return 1; | |||
| }, | |||
| [](dds::e_system_error_exc e, dds::e_repo_import_targz tgz) { | |||
| dds_log(error, "Failed to import package archive {}: {}", tgz.path, e.message); | |||
| return 1; | |||
| }, | |||
| [](dds::e_system_error_exc e, dds::e_open_repo_db db) { | |||
| dds_log(error, | |||
| "Error while opening repository database {}: {}", | |||
| db.path, | |||
| e.message); | |||
| return 1; | |||
| }, | |||
| [](dds::e_sqlite3_error_exc e, dds::e_init_repo init) { | |||
| dds_log(error, | |||
| "SQLite error while initializing repository in [{}]: {}", | |||
| init.path, | |||
| e.message); | |||
| return 1; | |||
| }, | |||
| [](dds::e_system_error_exc e, dds::e_repo_delete_targz tgz, dds::package_id pkg_id) { | |||
| dds_log(error, | |||
| "Cannot delete requested package '{}' from repository (Archive {}): {}", | |||
| pkg_id.to_string(), | |||
| tgz.path, | |||
| e.message); | |||
| return 1; | |||
| }, | |||
| [](dds::e_system_error_exc e) { | |||
| dds_log(error, "Unhandled system_error: {}", e.message); | |||
| return 1; | |||
| }, | |||
| [](boost::leaf::diagnostic_info const& info) { | |||
| dds_log(error, "Unknown error: {}", info); | |||
| return 42; | |||
| }); | |||
| } | |||
| }; | |||
| /* | |||
| ######## ######## ######## ####### | |||
| ## ## ## ## ## ## ## | |||
| @@ -910,6 +1111,7 @@ int main_fn(const std::vector<std::string>& argv) { | |||
| cli_build build{cli}; | |||
| cli_sdist sdist{cli}; | |||
| cli_repo repo{cli}; | |||
| cli_repoman repoman{cli}; | |||
| cli_catalog catalog{cli}; | |||
| cli_build_deps build_deps{cli}; | |||
| @@ -939,6 +1141,8 @@ int main_fn(const std::vector<std::string>& argv) { | |||
| return sdist.run(); | |||
| } else if (repo.cmd) { | |||
| return repo.run(); | |||
| } else if (repoman.cmd) { | |||
| return repoman.run(); | |||
| } else if (catalog.cmd) { | |||
| return catalog.run(); | |||
| } else if (build_deps.cmd) { | |||
| @@ -103,9 +103,14 @@ package_manifest parse_json(const json5::data& data, std::string_view fpath) { | |||
| package_manifest package_manifest::load_from_file(const fs::path& fpath) { | |||
| auto content = slurp_file(fpath); | |||
| auto data = json5::parse_data(content); | |||
| return load_from_json5_str(content, fpath.string()); | |||
| } | |||
| package_manifest package_manifest::load_from_json5_str(std::string_view content, | |||
| std::string_view input_name) { | |||
| auto data = json5::parse_data(content); | |||
| try { | |||
| return parse_json(data, fpath.string()); | |||
| return parse_json(data, input_name); | |||
| } catch (const semester::walk_error& e) { | |||
| throw_user_error<errc::invalid_pkg_manifest>(e.what()); | |||
| } | |||
| @@ -35,6 +35,10 @@ struct package_manifest { | |||
| * Load a package manifest from a file on disk. | |||
| */ | |||
| static package_manifest load_from_file(path_ref); | |||
| /** | |||
| * @brief Load a package manifest from an in-memory string | |||
| */ | |||
| static package_manifest load_from_json5_str(std::string_view, std::string_view input_name); | |||
| /** | |||
| * Find a package manifest contained within a directory. This will search | |||
| @@ -0,0 +1,216 @@ | |||
| #include "./repoman.hpp" | |||
| #include <dds/package/manifest.hpp> | |||
| #include <dds/util/log.hpp> | |||
| #include <dds/util/result.hpp> | |||
| #include <neo/gzip.hpp> | |||
| #include <neo/inflate.hpp> | |||
| #include <neo/io/stream/buffers.hpp> | |||
| #include <neo/io/stream/file.hpp> | |||
| #include <neo/sqlite3/exec.hpp> | |||
| #include <neo/sqlite3/single.hpp> | |||
| #include <neo/sqlite3/transaction.hpp> | |||
| #include <neo/tar/ustar.hpp> | |||
| #include <neo/transform_io.hpp> | |||
| #include <neo/utility.hpp> | |||
| #include <nlohmann/json.hpp> | |||
| using namespace dds; | |||
| namespace nsql = neo::sqlite3; | |||
| using namespace nsql::literals; | |||
| namespace { | |||
| void migrate_db_1(nsql::database_ref db) { | |||
| db.exec(R"( | |||
| CREATE TABLE dds_repo_packages ( | |||
| package_id INTEGER PRIMARY KEY, | |||
| name TEXT NOT NULL, | |||
| version TEXT NOT NULL, | |||
| description TEXT NOT NULL, | |||
| UNIQUE (name, version) | |||
| ); | |||
| CREATE TABLE dds_repo_package_deps ( | |||
| dep_id INTEGER PRIMARY KEY, | |||
| package_id INTEGER NOT NULL | |||
| REFERENCES dds_repo_packages | |||
| ON DELETE CASCADE, | |||
| dep_name TEXT NOT NULL, | |||
| low TEXT NOT NULL, | |||
| high TEXT NOT NULL, | |||
| UNIQUE(package_id, dep_name) | |||
| ); | |||
| )"); | |||
| } | |||
| void ensure_migrated(nsql::database_ref db, std::optional<std::string_view> name) { | |||
| db.exec(R"( | |||
| PRAGMA foreign_keys = 1; | |||
| CREATE TABLE IF NOT EXISTS dds_repo_meta ( | |||
| meta_version INTEGER DEFAULT 1, | |||
| version INTEGER NOT NULL, | |||
| name TEXT NOT NULL | |||
| ); | |||
| -- Insert the initial metadata | |||
| INSERT INTO dds_repo_meta (version, name) | |||
| SELECT 0, 'dds-repo-' || lower(hex(randomblob(6))) | |||
| WHERE NOT EXISTS (SELECT 1 FROM dds_repo_meta); | |||
| )"); | |||
| nsql::transaction_guard tr{db}; | |||
| auto meta_st = db.prepare("SELECT version FROM dds_repo_meta"); | |||
| auto [version] = nsql::unpack_single<int>(meta_st); | |||
| constexpr int current_database_version = 1; | |||
| if (version < 1) { | |||
| migrate_db_1(db); | |||
| } | |||
| nsql::exec(db.prepare("UPDATE dds_repo_meta SET version=?"), current_database_version); | |||
| if (name) { | |||
| nsql::exec(db.prepare("UPDATE dds_repo_meta SET name=?"), *name); | |||
| } | |||
| } | |||
| } // namespace | |||
| repo_manager repo_manager::create(path_ref directory, std::optional<std::string_view> name) { | |||
| { | |||
| DDS_E_SCOPE(e_init_repo{directory}); | |||
| fs::create_directories(directory); | |||
| auto db_path = directory / "repo.db"; | |||
| auto db = nsql::database::open(db_path.string()); | |||
| DDS_E_SCOPE(e_init_repo_db{db_path}); | |||
| DDS_E_SCOPE(e_open_repo_db{db_path}); | |||
| ensure_migrated(db, name); | |||
| fs::create_directories(directory / "data"); | |||
| } | |||
| return open(directory); | |||
| } | |||
| repo_manager repo_manager::open(path_ref directory) { | |||
| DDS_E_SCOPE(e_open_repo{directory}); | |||
| auto db_path = directory / "repo.db"; | |||
| DDS_E_SCOPE(e_open_repo_db{db_path}); | |||
| if (!fs::is_regular_file(db_path)) { | |||
| throw std::system_error(make_error_code(std::errc::no_such_file_or_directory), | |||
| "The database file does not exist"); | |||
| } | |||
| auto db = nsql::database::open(db_path.string()); | |||
| ensure_migrated(db, std::nullopt); | |||
| return repo_manager{fs::canonical(directory), std::move(db)}; | |||
| } | |||
| std::string repo_manager::name() const noexcept { | |||
| auto [name] = nsql::unpack_single<std::string>(_stmts("SELECT name FROM dds_repo_meta"_sql)); | |||
| return name; | |||
| } | |||
| void repo_manager::import_targz(path_ref tgz_file) { | |||
| neo_assertion_breadcrumbs("Importing targz file", tgz_file.string()); | |||
| DDS_E_SCOPE(e_repo_import_targz{tgz_file}); | |||
| dds_log(info, "Importing sdist archive [{}]", tgz_file.string()); | |||
| neo::ustar_reader tar{ | |||
| neo::buffer_transform_source{neo::stream_io_buffers{ | |||
| neo::file_stream::open(tgz_file, neo::open_mode::read)}, | |||
| neo::gzip_decompressor{neo::inflate_decompressor{}}}}; | |||
| std::optional<package_manifest> man; | |||
| for (auto mem : tar) { | |||
| if (fs::path(mem.filename_str()).lexically_normal() | |||
| == neo::oper::none_of("package.jsonc", "package.json5", "package.json")) { | |||
| continue; | |||
| } | |||
| auto content = tar.all_data(); | |||
| auto synth_filename = tgz_file / mem.filename_str(); | |||
| man = package_manifest::load_from_json5_str(std::string_view(content), | |||
| synth_filename.string()); | |||
| break; | |||
| } | |||
| if (!man) { | |||
| dds_log(critical, | |||
| "Given archive [{}] does not contain a package manifest file", | |||
| tgz_file.string()); | |||
| throw std::runtime_error("Invalid package archive"); | |||
| } | |||
| DDS_E_SCOPE(man->pkg_id); | |||
| neo::sqlite3::transaction_guard tr{_db}; | |||
| dds_log(debug, "Recording package {}@{}", man->pkg_id.name, man->pkg_id.version.to_string()); | |||
| nsql::exec( // | |||
| _stmts(R"( | |||
| INSERT INTO dds_repo_packages (name, version, description) | |||
| VALUES (?, ?, 'No description') | |||
| )"_sql), | |||
| man->pkg_id.name, | |||
| man->pkg_id.version.to_string()); | |||
| auto package_id = _db.last_insert_rowid(); | |||
| auto& insert_dep_st = _stmts(R"( | |||
| INSERT INTO dds_repo_package_deps(package_id, dep_name, low, high) | |||
| VALUES (?, ?, ?, ?) | |||
| )"_sql); | |||
| for (auto& dep : man->dependencies) { | |||
| assert(dep.versions.num_intervals() == 1); | |||
| auto iv_1 = *dep.versions.iter_intervals().begin(); | |||
| dds_log(trace, " Depends on: {}", dep.to_string()); | |||
| nsql::exec(insert_dep_st, | |||
| package_id, | |||
| dep.name, | |||
| iv_1.low.to_string(), | |||
| iv_1.high.to_string()); | |||
| } | |||
| auto dest_dir = data_dir() / man->pkg_id.name; | |||
| auto dest_path = dest_dir / fmt::format("{}.tar.gz", man->pkg_id.version.to_string()); | |||
| fs::create_directories(dest_dir); | |||
| fs::copy(tgz_file, dest_path); | |||
| tr.commit(); | |||
| } | |||
| void repo_manager::delete_package(package_id pkg_id) { | |||
| neo::sqlite3::transaction_guard tr{_db}; | |||
| DDS_E_SCOPE(pkg_id); | |||
| nsql::exec( // | |||
| _stmts(R"( | |||
| DELETE FROM dds_repo_packages | |||
| WHERE name = ? | |||
| AND version = ? | |||
| )"_sql), | |||
| pkg_id.name, | |||
| pkg_id.version.to_string()); | |||
| /// XXX: Verify with _db.changes() that we actually deleted one row | |||
| auto name_dir = data_dir() / pkg_id.name; | |||
| auto ver_file = name_dir / fmt::format("{}.tar.gz", pkg_id.version.to_string()); | |||
| DDS_E_SCOPE(e_repo_delete_targz{ver_file}); | |||
| if (!fs::is_regular_file(ver_file)) { | |||
| throw std::system_error(std::make_error_code(std::errc::no_such_file_or_directory), | |||
| "No source archive for the requested package"); | |||
| } | |||
| fs::remove(ver_file); | |||
| tr.commit(); | |||
| std::error_code ec; | |||
| fs::remove(name_dir, ec); | |||
| if (ec && ec != std::errc::directory_not_empty) { | |||
| throw std::system_error(ec, "Failed to delete package name directory"); | |||
| } | |||
| } | |||
| @@ -0,0 +1,70 @@ | |||
| #pragma once | |||
| #include <dds/package/id.hpp> | |||
| #include <dds/util/fs.hpp> | |||
| #include <neo/sqlite3/database.hpp> | |||
| #include <neo/sqlite3/iter_tuples.hpp> | |||
| #include <neo/sqlite3/statement_cache.hpp> | |||
| #include <range/v3/view/transform.hpp> | |||
| namespace dds { | |||
| struct e_init_repo { | |||
| fs::path path; | |||
| }; | |||
| struct e_open_repo { | |||
| fs::path path; | |||
| }; | |||
| struct e_init_repo_db { | |||
| fs::path path; | |||
| }; | |||
| struct e_open_repo_db { | |||
| fs::path path; | |||
| }; | |||
| struct e_repo_import_targz { | |||
| fs::path path; | |||
| }; | |||
| struct e_repo_delete_targz { | |||
| fs::path path; | |||
| }; | |||
| class repo_manager { | |||
| neo::sqlite3::database _db; | |||
| mutable neo::sqlite3::statement_cache _stmts{_db}; | |||
| fs::path _root; | |||
| explicit repo_manager(path_ref root, neo::sqlite3::database db) | |||
| : _db(std::move(db)) | |||
| , _root(root) {} | |||
| public: | |||
| repo_manager(repo_manager&&) = default; | |||
| static repo_manager create(path_ref directory, std::optional<std::string_view> name); | |||
| static repo_manager open(path_ref directory); | |||
| auto data_dir() const noexcept { return _root / "data"; } | |||
| path_ref root() const noexcept { return _root; } | |||
| std::string name() const noexcept; | |||
| void import_targz(path_ref tgz_path); | |||
| void delete_package(package_id id); | |||
| auto all_packages() const noexcept { | |||
| using namespace neo::sqlite3::literals; | |||
| auto& st = _stmts("SELECT name, version FROM dds_repo_packages"_sql); | |||
| auto tups = neo::sqlite3::iter_tuples<std::string, std::string>(st); | |||
| return tups | ranges::views::transform([](auto&& pair) { | |||
| auto [name, version] = pair; | |||
| return package_id{name, semver::version::parse(version)}; | |||
| }); | |||
| } | |||
| }; | |||
| } // namespace dds | |||
| @@ -0,0 +1,31 @@ | |||
| #include <dds/repoman/repoman.hpp> | |||
| #include <dds/temp.hpp> | |||
| #include <neo/sqlite3/error.hpp> | |||
| #include <catch2/catch.hpp> | |||
| namespace { | |||
| const auto THIS_FILE = dds::fs::canonical(__FILE__); | |||
| const auto THIS_DIR = THIS_FILE.parent_path(); | |||
| const auto REPO_ROOT = (THIS_DIR / "../../../").lexically_normal(); | |||
| const auto DATA_DIR = REPO_ROOT / "data"; | |||
| } // namespace | |||
| TEST_CASE("Open a repository") { | |||
| auto tdir = dds::temporary_dir::create(); | |||
| auto repo = dds::repo_manager::create(tdir.path(), "test-repo"); | |||
| auto neo_url_tgz = DATA_DIR / "neo-url@0.2.1.tar.gz"; | |||
| repo.import_targz(neo_url_tgz); | |||
| CHECK(dds::fs::is_directory(repo.data_dir() / "neo-url/")); | |||
| CHECK(dds::fs::is_regular_file(repo.data_dir() / "neo-url/0.2.1.tar.gz")); | |||
| CHECK_THROWS_AS(repo.import_targz(neo_url_tgz), neo::sqlite3::constraint_unique_error); | |||
| repo.delete_package(dds::package_id::parse("neo-url@0.2.1")); | |||
| CHECK_FALSE(dds::fs::is_regular_file(repo.data_dir() / "neo-url/0.2.1.tar.gz")); | |||
| CHECK_FALSE(dds::fs::is_directory(repo.data_dir() / "neo-url")); | |||
| CHECK_THROWS_AS(repo.delete_package(dds::package_id::parse("neo-url@0.2.1")), | |||
| std::system_error); | |||
| CHECK_NOTHROW(repo.import_targz(neo_url_tgz)); | |||
| } | |||
| @@ -0,0 +1,15 @@ | |||
| #include "./result.hpp" | |||
| #include <neo/sqlite3/error.hpp> | |||
| dds::error_id dds::capture_exception() { | |||
| try { | |||
| throw; | |||
| } catch (const neo::sqlite3::sqlite3_error& e) { | |||
| return current_error().load(e_sqlite3_error_exc{std::string(e.what()), e.code()}, | |||
| e.code(), | |||
| neo::sqlite3::errc{e.code().value()}); | |||
| } catch (const std::system_error& e) { | |||
| return current_error().load(e_system_error_exc{std::string(e.what()), e.code()}, e.code()); | |||
| } | |||
| } | |||
| @@ -0,0 +1,46 @@ | |||
| #pragma once | |||
| #include <neo/pp.hpp> | |||
| #include <boost/leaf/on_error.hpp> | |||
| #include <boost/leaf/result.hpp> | |||
| #include <exception> | |||
| #include <string> | |||
| namespace dds { | |||
| using boost::leaf::current_error; | |||
| using boost::leaf::error_id; | |||
| using boost::leaf::new_error; | |||
| using boost::leaf::result; | |||
| /** | |||
| * @brief Error object representing a captured system_error exception | |||
| */ | |||
| struct e_system_error_exc { | |||
| std::string message; | |||
| std::error_code code; | |||
| }; | |||
| /** | |||
| * @brief Error object representing a captured neo::sqlite3::sqlite3_error | |||
| */ | |||
| struct e_sqlite3_error_exc { | |||
| std::string message; | |||
| std::error_code code; | |||
| }; | |||
| /** | |||
| * @brief Capture currently in-flight special exceptions as new error object. Works around a bug in | |||
| * Boost.LEAF when catching std::system error. | |||
| */ | |||
| error_id capture_exception(); | |||
| /** | |||
| * @brief Generate a leaf::on_error object that loads the given expression into the currently | |||
| * in-flight error if the current scope is exitted via exception or a bad result<> | |||
| */ | |||
| #define DDS_E_SCOPE(...) \ | |||
| auto NEO_CONCAT(_err_info_, __LINE__) = boost::leaf::on_error([&] { return __VA_ARGS__; }) | |||
| } // namespace dds | |||