From 277ca41f9fd0d56868e9c6b7855216b28ab81cf1 Mon Sep 17 00:00:00 2001 From: Mirco Valentini Date: Mon, 13 Apr 2026 07:36:17 +0000 Subject: [PATCH] WIP: improve exception handling and prepare for code dump --- src/metkit/mars2grib/CoreOperations.h | 111 +++++++- src/metkit/mars2grib/api/Mars2Grib.cc | 132 +++++++-- src/metkit/mars2grib/debug/MockupDictionary.h | 42 +++ src/metkit/mars2grib/debug/reproducer.h | 256 ++++++++++++++++++ .../dictaccess_mock_reproducer.h | 180 ++++++++++++ .../dictionary_traits/dictaccess_options.h | 181 +++++++++++++ .../mars2grib/utils/mars2gribExceptions.h | 59 ++-- 7 files changed, 904 insertions(+), 57 deletions(-) create mode 100644 src/metkit/mars2grib/debug/MockupDictionary.h create mode 100644 src/metkit/mars2grib/debug/reproducer.h create mode 100644 src/metkit/mars2grib/utils/dictionary_traits/dictaccess_mock_reproducer.h create mode 100644 src/metkit/mars2grib/utils/dictionary_traits/dictaccess_options.h diff --git a/src/metkit/mars2grib/CoreOperations.h b/src/metkit/mars2grib/CoreOperations.h index ea89c7f3..c73b62c5 100644 --- a/src/metkit/mars2grib/CoreOperations.h +++ b/src/metkit/mars2grib/CoreOperations.h @@ -29,6 +29,15 @@ #include #include #include +#include +#include + +#if defined(__APPLE__) && defined(__MACH__) + #include +#elif defined(__linux__) + #include +#endif + /// Project includes /// @note: clang-format needs to be off here to preserve the logical grouping of @@ -47,6 +56,52 @@ namespace metkit::mars2grib { +namespace detail { + +/// @brief Generates a strictly formatted log filename safely (Linux/macOS only). +/// Guarantees no exceptions will escape and crash the simulation. +inline std::string generateEncodingLogFilename() noexcept { + try { + // 1. Get true Process ID + pid_t pid = getpid(); + + // 2. Get true OS Thread ID + unsigned long long tid = 0; + +#if defined(__APPLE__) && defined(__MACH__) + uint64_t mac_tid; + pthread_threadid_np(NULL, &mac_tid); + tid = static_cast(mac_tid); +#elif defined(__linux__) + // SYS_gettid is universally supported on Linux, even on old glibc versions + tid = static_cast(syscall(SYS_gettid)); +#else + // Ultimate fallback if compiled on an unknown UNIX variant + tid = 0; +#endif + + // 3. Format the string: encodingStack_%06d_%06llu.txt + // %06d ensures at least 6 digits with leading zeros (equivalent to %6.6d) + char buffer[64]; + int written = std::snprintf(buffer, sizeof(buffer), + "encodingStack_%06d_%06llu.txt", + static_cast(pid), tid); + + if (written > 0 && static_cast(written) < sizeof(buffer)) { + return std::string(buffer); + } + + return "encodingStack_fallback.txt"; + + } catch (...) { + // Catches std::bad_alloc if std::string creation fails due to OOM + return "encodingStack_fallback.txt"; + } +} + +} // namespace detail + + /// /// @brief Internal engine providing atomic encoding and diagnostic services. /// @@ -86,10 +141,10 @@ struct CoreOperations { return {activeMars, activePar}; } - catch (const std::exception& e) { + catch (...) { // Wrap any exception in a CoreOperations-specific exception to provide context std::throw_with_nested( - mars2grib::utils::exceptions::Mars2GribGenericException("Error during metadata normalization", Here())); + mars2grib::utils::exceptions::Mars2GribCoreException("Error during metadata normalization", Here())); } } @@ -118,10 +173,10 @@ struct CoreOperations { return SpecializedEncoder{std::move(layout)}.encode(mars, misc, opt); } - catch (const std::exception& e) { + catch (...) { // Wrap any exception in a CoreOperations-specific exception to provide context std::throw_with_nested( - mars2grib::utils::exceptions::Mars2GribGenericException("Error during header encoding", Here())); + mars2grib::utils::exceptions::Mars2GribCoreException("Error during header encoding", Here())); } } @@ -143,10 +198,10 @@ struct CoreOperations { metkit::mars2grib::backend::encodeValues(values, misc, opt, *handle); return handle; } - catch (const std::exception& e) { + catch (...) { // Wrap any exception in a CoreOperations-specific exception to provide context std::throw_with_nested( - mars2grib::utils::exceptions::Mars2GribGenericException("Error during value encoding", Here())); + mars2grib::utils::exceptions::Mars2GribCoreException("Error during value encoding", Here())); } } @@ -251,14 +306,10 @@ struct CoreOperations { // 4. Inject Values return encodeValues(values, activeMisc, options, std::move(gribHeader)); } - catch (const std::exception& e) { - printExtendedStack(e); - throw; - } catch (...) { - // Fallback for non-standard exceptions - throw metkit::mars2grib::utils::exceptions::Mars2GribGenericException("Unknown error during encoding", - Here()); + // Wrap any exception in a CoreOperations-specific exception to provide context + std::throw_with_nested( + mars2grib::utils::exceptions::Mars2GribCoreException("Error during encoding", Here())); } } @@ -281,12 +332,42 @@ struct CoreOperations { return debug_convert_GribHeaderLayoutData_to_json(layout); } - catch (const std::exception& e) { + catch (...) { // Wrap any exception in a CoreOperations-specific exception to provide context std::throw_with_nested( - mars2grib::utils::exceptions::Mars2GribGenericException("Error during header test dump", Here())); + mars2grib::utils::exceptions::Mars2GribCoreException("Error during header test dump", Here())); } } + + static std::string generateStack( const std::exception& e, const std::string& msg, const eckit::CodeLocation& loc ){ + + using metkit::mars2grib::utils::exceptions::printExtendedStack; + using metkit::mars2grib::utils::exceptions::lineSize; + + const std::string logName = detail::generateEncodingLogFilename(); + + std::ofstream out(logName); + std::size_t level = 0; + std::size_t frame = 1; + + out << "+ " << std::string(lineSize, '=') << std::endl + << "+ frame " << frame << std::endl + << "+ " << std::string(lineSize, '-') << std::endl; + + out << "+ file: " << loc.file() << "\n" + << "+ function: " << loc.func() << "\n" + << "+ line: " << loc.line() << "\n" + << "+ link: " << loc.file() << ":" << loc.line() << "\n" + << "+ message: " << msg << "\n"; + + out << "+ " << std::string(lineSize, '+') << std::endl; + + printExtendedStack( e, out, ++level, ++frame ); + + return "test.log"; + + } + }; } // namespace metkit::mars2grib \ No newline at end of file diff --git a/src/metkit/mars2grib/api/Mars2Grib.cc b/src/metkit/mars2grib/api/Mars2Grib.cc index 95274bf6..b73d341f 100644 --- a/src/metkit/mars2grib/api/Mars2Grib.cc +++ b/src/metkit/mars2grib/api/Mars2Grib.cc @@ -120,61 +120,145 @@ Mars2Grib::Mars2Grib(const eckit::LocalConfiguration& opts) : opts_{readOptions( std::unique_ptr Mars2Grib::encode(const std::vector& values, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc) { - return CoreOperations::encode(Span{values}, mars, misc, opts_, language_); + try { + return CoreOperations::encode(Span{values}, mars, misc, opts_, + language_); + } + catch (const std::exception& e) { + std::string logFile = CoreOperations::generateStack( e, "Error during API::encode call", Here() ); + throw eckit::Exception("Error during encoding", Here()); + } + catch (...) { + // Fallback for non-standard exceptions + throw eckit::Exception("Unknown error during encoding", Here()); + } } std::unique_ptr Mars2Grib::encode(const std::vector& values, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc) { - return CoreOperations::encode(Span{values}, mars, misc, opts_, language_); + try { + return CoreOperations::encode(Span{values}, mars, misc, opts_, + language_); + } + catch (const std::exception& e) { + std::string logFile = CoreOperations::generateStack( e, "Error during API::encode call", Here() ); + throw eckit::Exception("Error during encoding", Here()); + } + catch (...) { + // Fallback for non-standard exceptions + throw eckit::Exception("Unknown error during encoding", Here()); + } } std::unique_ptr Mars2Grib::encode(const std::vector& values, const eckit::LocalConfiguration& mars) { - const eckit::LocalConfiguration misc{}; - return CoreOperations::encode(Span{values}, mars, misc, opts_, language_); + try { + const eckit::LocalConfiguration misc{}; + return CoreOperations::encode(Span{values}, mars, misc, opts_, + language_); + } + catch (const std::exception& e) { + std::string logFile = CoreOperations::generateStack( e, "Error during API::encode call", Here() ); + throw eckit::Exception("Error during encoding", Here()); + } + catch (...) { + // Fallback for non-standard exceptions + throw eckit::Exception("Unknown error during encoding", Here()); + } } std::unique_ptr Mars2Grib::encode(const std::vector& values, const eckit::LocalConfiguration& mars) { - const eckit::LocalConfiguration misc{}; - return CoreOperations::encode(Span{values}, mars, misc, opts_, language_); + try { + const eckit::LocalConfiguration misc{}; + return CoreOperations::encode(Span{values}, mars, misc, opts_, + language_); + } + catch (const std::exception& e) { + std::string logFile = CoreOperations::generateStack( e, "Error during API::encode call", Here() ); + throw eckit::Exception("Error during encoding", Here()); + } + catch (...) { + // Fallback for non-standard exceptions + throw eckit::Exception("Unknown error during encoding", Here()); + } } std::unique_ptr Mars2Grib::encode(const double* values, size_t length, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc) { - return CoreOperations::encode(Span{values, length}, mars, misc, opts_, - language_); + try { + return CoreOperations::encode(Span{values, length}, mars, misc, opts_, + language_); + } + catch (const std::exception& e) { + std::string logFile = CoreOperations::generateStack( e, "Error during API::encode call", Here() ); + throw eckit::Exception("Error during encoding", Here()); + } + catch (...) { + // Fallback for non-standard exceptions + throw eckit::Exception("Unknown error during encoding", Here()); + } } std::unique_ptr Mars2Grib::encode(const float* values, size_t length, const eckit::LocalConfiguration& mars, const eckit::LocalConfiguration& misc) { - return CoreOperations::encode(Span{values, length}, mars, misc, opts_, - language_); + try { + return CoreOperations::encode(Span{values, length}, mars, misc, opts_, + language_); + } + catch (const std::exception& e) { + std::string logFile = CoreOperations::generateStack( e, "Error during API::encode call", Here() ); + throw eckit::Exception("Error during encoding", Here()); + } + catch (...) { + // Fallback for non-standard exceptions + throw eckit::Exception("Unknown error during encoding", Here()); + } } std::unique_ptr Mars2Grib::encode(const double* values, size_t length, const eckit::LocalConfiguration& mars) { - const eckit::LocalConfiguration misc{}; - return CoreOperations::encode(Span{values, length}, mars, misc, opts_, - language_); + try { + const eckit::LocalConfiguration misc{}; + return CoreOperations::encode(Span{values, length}, mars, misc, opts_, + language_); + } + catch (const std::exception& e) { + std::string logFile = CoreOperations::generateStack( e, "Error during API::encode call", Here() ); + throw eckit::Exception("Error during encoding", Here()); + } + catch (...) { + // Fallback for non-standard exceptions + throw eckit::Exception("Unknown error during encoding", Here()); + } } std::unique_ptr Mars2Grib::encode(const float* values, size_t length, const eckit::LocalConfiguration& mars) { - const eckit::LocalConfiguration misc{}; - return CoreOperations::encode(Span{values, length}, mars, misc, opts_, - language_); + try { + const eckit::LocalConfiguration misc{}; + return CoreOperations::encode(Span{values, length}, mars, misc, opts_, + language_); + } + catch (const std::exception& e) { + std::string logFile = CoreOperations::generateStack( e, "Error during API::encode call", Here() ); + throw eckit::Exception("Error during encoding", Here()); + } + catch (...) { + // Fallback for non-standard exceptions + throw eckit::Exception("Unknown error during encoding", Here()); + } } } // namespace metkit::mars2grib diff --git a/src/metkit/mars2grib/debug/MockupDictionary.h b/src/metkit/mars2grib/debug/MockupDictionary.h new file mode 100644 index 00000000..074aafbb --- /dev/null +++ b/src/metkit/mars2grib/debug/MockupDictionary.h @@ -0,0 +1,42 @@ +#include +#include + +#include "metkit/mars2grib/debug/reproducer.h" +#include "metkit/codes/api/CodesTypes.h" + +// ============================================================================ +// MockDictionary Definition +// ============================================================================ +namespace metkit::mars2grib::debug::reproducer { + + +class MockDictionary { +public: + std::shared_ptr recorder; + + // Pipeline constructor + explicit MockDictionary(std::shared_ptr rec) + : recorder(std::move(rec)) {} + + // Output utility + void dump(const std::string& filename) const { + if (recorder) recorder->generate(filename); + } + + template + void set(const std::string& k, const T& v) { + recorder->record(k, v); + } + + template + void set_array(const std::string& k, const std::vector& v) { + recorder->record_array(k, v.data(), v.size()); + } + + template + void set_array(const std::string& k, const metkit::codes::Span& v) { + recorder->record_array(k, v.data(), v.size()); + } +}; + +} // namespace metkit::mars2grib::debug::reproducer \ No newline at end of file diff --git a/src/metkit/mars2grib/debug/reproducer.h b/src/metkit/mars2grib/debug/reproducer.h new file mode 100644 index 00000000..0d3586d2 --- /dev/null +++ b/src/metkit/mars2grib/debug/reproducer.h @@ -0,0 +1,256 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace metkit::mars2grib::debug::reproducer { + +constexpr size_t BINARY_THRESHOLD = 5000; +constexpr uint32_t ENDIAN_SENTINEL = 0x12345678; + +// ----------------------------------------------------------------------------- +// Base Operation Interface +// ----------------------------------------------------------------------------- +class Operation { +public: + virtual ~Operation() = default; + virtual void emitC_Statics(std::ostream& out, int id) const {} + virtual void emitC_Logic(std::ostream& out, int id) const = 0; +}; + +// ----------------------------------------------------------------------------- +// Lifecycle Operations (The "Commit" Flush) +// ----------------------------------------------------------------------------- +class OpFromSample : public Operation { + std::string sample_; +public: + explicit OpFromSample(std::string s) : sample_(std::move(s)) {} + void emitC_Logic(std::ostream& out, int /*id*/) const override { + out << " h = codes_grib_handle_new_from_samples(0, \"" << sample_ << "\");\n" + << " if(!h) { fprintf(stderr, \"Failed to create from sample\\n\"); exit(1); }\n"; + } +}; + +class OpClone : public Operation { +public: + void emitC_Logic(std::ostream& out, int /*id*/) const override { + out << " // --- FLUSH/COMMIT BARRIER (Clone) ---\n" + << " {\n" + << " codes_handle* temp_h = codes_handle_clone(h);\n" + << " if(!temp_h) { fprintf(stderr, \"Clone flush failed\\n\"); exit(1); }\n" + << " codes_handle_delete(h);\n" + << " h = temp_h;\n" + << " }\n"; + } +}; + +// ----------------------------------------------------------------------------- +// Missing Value +// ----------------------------------------------------------------------------- +class OpMissing : public Operation { + std::string key_; +public: + explicit OpMissing(std::string k) : key_(std::move(k)) {} + void emitC_Logic(std::ostream& out, int /*id*/) const override { + out << " CODES_CHECK(codes_set_missing(h, \"" << key_ << "\"), 0);\n"; + } +}; + +// ----------------------------------------------------------------------------- +// Scalars +// ----------------------------------------------------------------------------- +template +class OpScalar : public Operation { + std::string key_; + T val_; +public: + OpScalar(std::string k, T v) : key_(std::move(k)), val_(std::move(v)) {} + void emitC_Logic(std::ostream& out, int /*id*/) const override { + out << " CODES_CHECK(codes_set_"; + if constexpr (std::is_same_v || std::is_same_v || std::is_same_v) { + out << "long(h, \"" << key_ << "\", " << static_cast(val_) << "), 0);\n"; + } + else if constexpr (std::is_same_v) { + out << "double(h, \"" << key_ << "\", " << std::setprecision(17) << val_ << "), 0);\n"; + } + else if constexpr (std::is_same_v) { + out << "string(h, \"" << key_ << "\", \"" << val_ << "\", NULL), 0);\n"; + } + } +}; + +// ----------------------------------------------------------------------------- +// Small Arrays (Inlined C statics) +// ----------------------------------------------------------------------------- +template +class OpSmallArray : public Operation { + std::string key_; + std::vector vals_; +public: + OpSmallArray(std::string k, const T* data, size_t size) + : key_(std::move(k)), vals_(data, data + size) {} + + void emitC_Statics(std::ostream& out, int id) const override { + if constexpr (std::is_same_v || std::is_same_v) return; + + out << "static "; + if constexpr (std::is_same_v) out << "long "; + else out << "double "; + + out << "arr_" << id << "[] = {\n "; + for (size_t i = 0; i < vals_.size(); ++i) { + if constexpr (std::is_same_v) out << std::setprecision(17); + out << vals_[i] << (i + 1 == vals_.size() ? "" : ", "); + if ((i + 1) % 10 == 0 && i + 1 != vals_.size()) out << "\n "; + } + out << "\n};\n"; + } + + void emitC_Logic(std::ostream& out, int id) const override { + if constexpr (std::is_same_v || std::is_same_v) { + out << " // WARNING: Array emission for key '" << key_ << "' not fully implemented.\n"; + return; + } + out << " CODES_CHECK(codes_set_"; + if constexpr (std::is_same_v) out << "long_array"; + else out << "double_array"; + out << "(h, \"" << key_ << "\", arr_" << id << ", " << vals_.size() << "), 0);\n"; + } +}; + +// ----------------------------------------------------------------------------- +// Large Arrays (Zero-copy binary write) +// ----------------------------------------------------------------------------- +template +class OpLargeArray : public Operation { + std::string key_; + std::string bin_filename_; +public: + OpLargeArray(std::string k, const T* data, size_t size, int id) + : key_(std::move(k)), bin_filename_("data_" + std::to_string(id) + ".bin") + { + if constexpr (std::is_same_v || std::is_same_v) return; + + std::ofstream bin(bin_filename_, std::ios::binary); + if (!bin) throw std::runtime_error("Failed to open " + bin_filename_); + + uint32_t sentinel = ENDIAN_SENTINEL; + uint32_t type_sz = sizeof(T); + uint64_t n_elem = size; + + bin.write(reinterpret_cast(&sentinel), 4); + bin.write(reinterpret_cast(&type_sz), 4); + bin.write(reinterpret_cast(&n_elem), 8); + bin.write(reinterpret_cast(data), n_elem * type_sz); + } + + void emitC_Logic(std::ostream& out, int /*id*/) const override { + if constexpr (std::is_same_v || std::is_same_v) return; + + out << " {\n" + << " size_t n;\n" + << " void* ptr = load_payload(\"" << bin_filename_ << "\", &n, " << sizeof(T) << ");\n" + << " CODES_CHECK(codes_set_"; + if constexpr (std::is_same_v) out << "long_array"; + else out << "double_array"; + out << "(h, \"" << key_ << "\", ("; + if constexpr (std::is_same_v) out << "long*)ptr"; + else out << "double*)ptr"; + out << ", n), 0);\n" + << " free(ptr);\n" + << " }\n"; + } +}; + +// ----------------------------------------------------------------------------- +// The Shared Recorder Core +// ----------------------------------------------------------------------------- +class Recorder { + std::vector> ops_; + int op_counter_ = 0; + +public: + Recorder() = default; + + void record_from_sample(const std::string& sample_name) { + ops_.push_back(std::make_unique(sample_name)); + } + + void record_clone() { + ops_.push_back(std::make_unique()); + } + + void record_missing(const std::string& key) { + ops_.push_back(std::make_unique(key)); + } + + template + void record(const std::string& key, T val) { + ops_.push_back(std::make_unique>(key, std::move(val))); + } + + template + void record_array(const std::string& key, const T* data, size_t size) { + if (size > BINARY_THRESHOLD) { + ops_.push_back(std::make_unique>(key, data, size, ++op_counter_)); + } else { + ops_.push_back(std::make_unique>(key, data, size)); + ++op_counter_; + } + } + + void generate(const std::string& filename) const { + std::ofstream out(filename); + if (!out) throw std::runtime_error("Cannot open reproducer file."); + + out << R"C_CODE(#include +#include +#include +#include + +void* load_payload(const char* fn, size_t* n, size_t expected_sz) { + FILE* f = fopen(fn, "rb"); + if(!f) { perror(fn); exit(1); } + uint32_t s, sz; uint64_t c; + if (fread(&s, 4, 1, f) != 1 || fread(&sz, 4, 1, f) != 1 || fread(&c, 8, 1, f) != 1) exit(1); + if(sz != expected_sz) exit(1); + + void* d = malloc(c * sz); + if (fread(d, sz, c, f) != c) exit(1); + fclose(f); + + if(s == 0x78563412) { + char* ptr = (char*)d; + for (size_t i = 0; i < c * sz; i += sz) { + for (size_t j = 0; j < sz / 2; ++j) { + char tmp = ptr[i + j]; + ptr[i + j] = ptr[i + sz - 1 - j]; + ptr[i + sz - 1 - j] = tmp; + } + } + } else if (s != 0x12345678) exit(1); + *n = (size_t)c; + return d; +} +)C_CODE"; + + for (size_t i = 0; i < ops_.size(); ++i) ops_[i]->emitC_Statics(out, i); + + out << "\nint main() {\n" + << " codes_handle* h = NULL;\n\n"; + + for (size_t i = 0; i < ops_.size(); ++i) ops_[i]->emitC_Logic(out, i); + + out << "\n if(h) codes_handle_delete(h);\n" + << " return 0;\n}\n"; + } +}; + +} // namespace metkit::mars2grib::debug::reproducer \ No newline at end of file diff --git a/src/metkit/mars2grib/utils/dictionary_traits/dictaccess_mock_reproducer.h b/src/metkit/mars2grib/utils/dictionary_traits/dictaccess_mock_reproducer.h new file mode 100644 index 00000000..9905ffc3 --- /dev/null +++ b/src/metkit/mars2grib/utils/dictionary_traits/dictaccess_mock_reproducer.h @@ -0,0 +1,180 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +#include "metkit/codes/api/CodesTypes.h" +#include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" +#include "metkit/mars2grib/utils/mars2gribExceptions.h" +#include "metkit/mars2grib/utils/type_traits_name.h" +#include "metkit/mars2grib/debug/MockupDictionary.h" + +namespace metkit::mars2grib::utils { + +using MockDictionary = metkit::mars2grib::debug::reproducer::MockDictionary; + + +template <> +constexpr std::string_view type_name() { + return "metkit::mars2grib::debug::MockDictionary"; +} + +} // namespace metkit::mars2grib::utils + +namespace metkit::mars2grib::utils::dict_traits { + +using std::operator""s; + +#define M2G_DEFINE_MOCK_DICT_GET_OR_THROW(CTYPE) \ + template <> \ + struct DictGetOrThrow { \ + static CTYPE get_or_throw(const MockDictionary&, std::string_view key) noexcept(false) { \ + throw exceptions::Mars2GribDictException( \ + "Cannot read key `"s + std::string(key) + "` from write-only MockDictionary", \ + Here()); \ + } \ + }; + +#define M2G_DEFINE_MOCK_DICT_GET_OPT(CTYPE) \ + template <> \ + struct DictGetOpt { \ + static std::optional get_opt(const MockDictionary&, std::string_view) noexcept(false) { \ + return std::nullopt; \ + } \ + }; + +#define M2G_DEFINE_MOCK_DICT_SET_OR_THROW(CTYPE, SETFUNC) \ + template <> \ + struct DictSetOrThrow { \ + static void set_or_throw(MockDictionary& d, std::string_view key, \ + const CTYPE& v) noexcept(false) { \ + const std::string k{key}; \ + try { \ + d.SETFUNC(k, v); \ + } catch (const std::exception& e) { \ + std::throw_with_nested(exceptions::Mars2GribDictException( \ + "Mock recorder failed on key `"s + k + "`: "s + e.what(), Here())); \ + } \ + } \ + }; + +#define M2G_DEFINE_MOCK_DICT_SET_OR_IGNORE(CTYPE, SETFUNC) \ + template <> \ + struct DictSetOrIgnore { \ + static void set_or_ignore(MockDictionary& d, std::string_view key, \ + const CTYPE& v) noexcept(false) { \ + try { d.SETFUNC(std::string{key}, v); } catch (...) {} \ + } \ + }; + +template <> +struct DictToJsonTraits { + static std::string to_json(const MockDictionary&) { + return std::string{"[to_json not supported for MockDictionary]"}; + } + static void dump_or_ignore(const MockDictionary& d, const std::string& fname) { + d.dump(fname); + } +}; + +// ----------------------------------------------------------------------------- +// DictTraits (Linear Shared Architecture) +// ----------------------------------------------------------------------------- +template <> +struct DictTraits { + static constexpr bool support_checks = false; + + // Starts the timeline + static std::unique_ptr make_from_sample_or_throw(std::string_view name) { + auto rec = std::make_shared(); + rec->record_from_sample(std::string(name)); + return std::make_unique(rec); + } + + // Injects a "flush" operation into the shared timeline + static std::unique_ptr clone_or_throw(const MockDictionary& other) { + other.recorder->record_clone(); + return std::make_unique(other.recorder); + } +}; + +template <> +struct DictHas { + static bool has(const MockDictionary&, std::string_view key) noexcept(false) { + throw exceptions::Mars2GribDictException("Cannot check key presence in MockDictionary", Here()); + } +}; + +template <> +struct DictMissing { + static bool isMissing(const MockDictionary&, std::string_view key) noexcept(false) { + throw exceptions::Mars2GribDictException("Cannot check missing state in MockDictionary", Here()); + } + static void setMissing(MockDictionary& d, std::string_view key) noexcept(false) { + try { d.recorder->record_missing(std::string(key)); } + catch (...) { + std::throw_with_nested(exceptions::Mars2GribDictException("Unable to set missing", Here())); + } + } +}; + +// ============================================================================ +// SCALAR TYPES +// ============================================================================ +M2G_DEFINE_MOCK_DICT_GET_OR_THROW(bool) +M2G_DEFINE_MOCK_DICT_GET_OPT(bool) +M2G_DEFINE_MOCK_DICT_SET_OR_THROW(bool, set) +M2G_DEFINE_MOCK_DICT_SET_OR_IGNORE(bool, set) + +M2G_DEFINE_MOCK_DICT_GET_OR_THROW(int) +M2G_DEFINE_MOCK_DICT_GET_OPT(int) +M2G_DEFINE_MOCK_DICT_SET_OR_THROW(int, set) +M2G_DEFINE_MOCK_DICT_SET_OR_IGNORE(int, set) + +M2G_DEFINE_MOCK_DICT_GET_OR_THROW(long) +M2G_DEFINE_MOCK_DICT_GET_OPT(long) +M2G_DEFINE_MOCK_DICT_SET_OR_THROW(long, set) +M2G_DEFINE_MOCK_DICT_SET_OR_IGNORE(long, set) + +M2G_DEFINE_MOCK_DICT_GET_OR_THROW(double) +M2G_DEFINE_MOCK_DICT_GET_OPT(double) +M2G_DEFINE_MOCK_DICT_SET_OR_THROW(double, set) +M2G_DEFINE_MOCK_DICT_SET_OR_IGNORE(double, set) + +M2G_DEFINE_MOCK_DICT_GET_OR_THROW(std::string) +M2G_DEFINE_MOCK_DICT_GET_OPT(std::string) +M2G_DEFINE_MOCK_DICT_SET_OR_THROW(std::string, set) +M2G_DEFINE_MOCK_DICT_SET_OR_IGNORE(std::string, set) + +// ============================================================================ +// VECTOR TYPES +// ============================================================================ +M2G_DEFINE_MOCK_DICT_GET_OR_THROW(std::vector) +M2G_DEFINE_MOCK_DICT_GET_OPT(std::vector) +M2G_DEFINE_MOCK_DICT_SET_OR_THROW(std::vector, set_array) +M2G_DEFINE_MOCK_DICT_SET_OR_IGNORE(std::vector, set_array) + +M2G_DEFINE_MOCK_DICT_GET_OR_THROW(std::vector) +M2G_DEFINE_MOCK_DICT_GET_OPT(std::vector) + +M2G_DEFINE_MOCK_DICT_SET_OR_THROW(metkit::codes::Span, set_array) +M2G_DEFINE_MOCK_DICT_SET_OR_THROW(std::vector, set_array) +M2G_DEFINE_MOCK_DICT_SET_OR_IGNORE(metkit::codes::Span, set_array) +M2G_DEFINE_MOCK_DICT_SET_OR_IGNORE(std::vector, set_array) + +M2G_DEFINE_MOCK_DICT_GET_OR_THROW(std::vector) +M2G_DEFINE_MOCK_DICT_GET_OPT(std::vector) +M2G_DEFINE_MOCK_DICT_SET_OR_THROW(std::vector, set_array) +M2G_DEFINE_MOCK_DICT_SET_OR_IGNORE(std::vector, set_array) + +M2G_DEFINE_MOCK_DICT_GET_OR_THROW(std::vector) +M2G_DEFINE_MOCK_DICT_GET_OPT(std::vector) +M2G_DEFINE_MOCK_DICT_SET_OR_THROW(std::vector, set_array) +M2G_DEFINE_MOCK_DICT_SET_OR_IGNORE(std::vector, set_array) + +} // namespace metkit::mars2grib::utils::dict_traits \ No newline at end of file diff --git a/src/metkit/mars2grib/utils/dictionary_traits/dictaccess_options.h b/src/metkit/mars2grib/utils/dictionary_traits/dictaccess_options.h new file mode 100644 index 00000000..2a800b91 --- /dev/null +++ b/src/metkit/mars2grib/utils/dictionary_traits/dictaccess_options.h @@ -0,0 +1,181 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "metkit/mars2grib/Options.h" +#include "metkit/mars2grib/utils/dictionary_traits/dictionary_access_traits.h" +#include "metkit/mars2grib/utils/mars2gribExceptions.h" +#include "metkit/mars2grib/utils/type_traits_name.h" + +namespace metkit::mars2grib::utils { + +template <> +constexpr std::string_view type_name() { + return "metkit::mars2grib::Options"; +} + +// ----------------------------------------------------------------------------- +// Internal helper to map string keys to Options struct members +// ----------------------------------------------------------------------------- +namespace detail { + inline bool* get_options_ptr(metkit::mars2grib::Options& opt, std::string_view key) { + if (key == "applyChecks") return &opt.applyChecks; + if (key == "enableOverride") return &opt.enableOverride; + if (key == "enableBitsPerValueCompression") return &opt.enableBitsPerValueCompression; + if (key == "normalizeMars") return &opt.normalizeMars; + if (key == "normalizeMisc") return &opt.normalizeMisc; + if (key == "fixMarsGrid") return &opt.fixMarsGrid; + return nullptr; + } + + inline const bool* get_options_ptr(const metkit::mars2grib::Options& opt, std::string_view key) { + if (key == "applyChecks") return &opt.applyChecks; + if (key == "enableOverride") return &opt.enableOverride; + if (key == "enableBitsPerValueCompression") return &opt.enableBitsPerValueCompression; + if (key == "normalizeMars") return &opt.normalizeMars; + if (key == "normalizeMisc") return &opt.normalizeMisc; + if (key == "fixMarsGrid") return &opt.fixMarsGrid; + return nullptr; + } +} // namespace detail + +} // namespace metkit::mars2grib::utils + + +namespace metkit::mars2grib::utils::dict_traits { + +using std::operator""s; + +// ----------------------------------------------------------------------------- +// DictTraits +// ----------------------------------------------------------------------------- +template <> +struct DictTraits { + + static constexpr bool support_checks = true; + + static std::unique_ptr make_from_sample_or_throw(std::string_view /*name*/) { + // Options has no samples, just return default constructed + return std::make_unique(); + } + + static std::unique_ptr clone_or_throw(const metkit::mars2grib::Options& h) { + // Struct has implicit copy constructor + return std::make_unique(h); + } +}; + +// ----------------------------------------------------------------------------- +// DictHas +// ----------------------------------------------------------------------------- +template <> +struct DictHas { + static bool has(const metkit::mars2grib::Options& opt, std::string_view key) noexcept(false) { + return detail::get_options_ptr(opt, key) != nullptr; + } +}; + +// ----------------------------------------------------------------------------- +// DictMissing (Options cannot be "missing", they are boolean flags) +// ----------------------------------------------------------------------------- +template <> +struct DictMissing { + static bool isMissing(const metkit::mars2grib::Options&, std::string_view) noexcept(false) { + return false; + } + + static void setMissing(metkit::mars2grib::Options&, std::string_view key) noexcept(false) { + throw exceptions::Mars2GribDictException( + "Cannot set key `"s + std::string(key) + "` to missing in dictionary type `"s + + std::string(type_name()) + "`", + Here()); + } +}; + +// ----------------------------------------------------------------------------- +// DictToJsonTraits +// ----------------------------------------------------------------------------- +template <> +struct DictToJsonTraits { + static std::string to_json(const metkit::mars2grib::Options& opt) { + return "{\n" + " \"applyChecks\": "s + (opt.applyChecks ? "true" : "false") + ",\n" + + " \"enableOverride\": "s + (opt.enableOverride ? "true" : "false") + ",\n" + + " \"enableBitsPerValueCompression\": "s + (opt.enableBitsPerValueCompression ? "true" : "false") + ",\n" + + " \"normalizeMars\": "s + (opt.normalizeMars ? "true" : "false") + ",\n" + + " \"normalizeMisc\": "s + (opt.normalizeMisc ? "true" : "false") + ",\n" + + " \"fixMarsGrid\": "s + (opt.fixMarsGrid ? "true" : "false") + "\n" + + "}"; + } + + static void dump_or_ignore(const metkit::mars2grib::Options&, const std::string&) { + // Not applicable for this simple struct + } +}; + +// ============================================================================ +// MACROS FOR TYPE MAPPING (Bool, Int, Long) +// ============================================================================ + +#define M2G_DEFINE_OPTIONS_DICT_ACCESS(CTYPE) \ + template <> \ + struct DictGetOrThrow { \ + static CTYPE get_or_throw(const metkit::mars2grib::Options& opt, std::string_view key) noexcept(false) { \ + if (const bool* ptr = detail::get_options_ptr(opt, key)) { \ + return static_cast(*ptr); \ + } \ + throw exceptions::Mars2GribDictException( \ + "Missing or invalid key `"s + std::string(key) + "` in dictionary `"s + \ + std::string(type_name()) + "`", Here()); \ + } \ + }; \ + \ + template <> \ + struct DictGetOpt { \ + static std::optional get_opt(const metkit::mars2grib::Options& opt, std::string_view key) noexcept(false) { \ + if (const bool* ptr = detail::get_options_ptr(opt, key)) { \ + return static_cast(*ptr); \ + } \ + return std::nullopt; \ + } \ + }; \ + \ + template <> \ + struct DictSetOrThrow { \ + static void set_or_throw(metkit::mars2grib::Options& opt, std::string_view key, const CTYPE& v) noexcept(false) { \ + if (bool* ptr = detail::get_options_ptr(opt, key)) { \ + *ptr = static_cast(v); \ + return; \ + } \ + throw exceptions::Mars2GribDictException( \ + "Unable to set key `"s + std::string(key) + "` in dictionary `"s + \ + std::string(type_name()) + "`", Here()); \ + } \ + }; \ + \ + template <> \ + struct DictSetOrIgnore { \ + static void set_or_ignore(metkit::mars2grib::Options& opt, std::string_view key, const CTYPE& v) noexcept(false) { \ + if (bool* ptr = detail::get_options_ptr(opt, key)) { \ + *ptr = static_cast(v); \ + } \ + } \ + }; + +// ============================================================================ +// APPLY TYPE SPECIALIZATIONS +// ============================================================================ + +M2G_DEFINE_OPTIONS_DICT_ACCESS(bool) +M2G_DEFINE_OPTIONS_DICT_ACCESS(int) +M2G_DEFINE_OPTIONS_DICT_ACCESS(long) + +// String, double, and vector types deliberately left undefined. +// They will fall through to the base traits and throw exceptions::Mars2GribDictException. + +} // namespace metkit::mars2grib::utils::dict_traits \ No newline at end of file diff --git a/src/metkit/mars2grib/utils/mars2gribExceptions.h b/src/metkit/mars2grib/utils/mars2gribExceptions.h index e6a52edb..c42f9e80 100644 --- a/src/metkit/mars2grib/utils/mars2gribExceptions.h +++ b/src/metkit/mars2grib/utils/mars2gribExceptions.h @@ -39,6 +39,8 @@ #include #include #include +#include +#include #include // Project includes @@ -71,11 +73,32 @@ class Mars2GribGenericException : public eckit::Exception, public std::nested_ex virtual ~Mars2GribGenericException() = default; - virtual void printFrame(const std::string& pad) const { + virtual void printFrame(std::ofstream& out, const std::string& pad) const { const auto& loc = location(); - LOG_DEBUG_LIB(LibMetkit) << pad << "+ file: " << loc.file() << "\n" + out << pad << "+ file: " << loc.file() << "\n" + << pad << "+ function: " << loc.func() << "\n" + << pad << "+ line: " << loc.line() << "\n" + << pad << "+ link: " << loc.file() << ":" << loc.line() << "\n" + << pad << "+ message: " << what() << "\n"; + } +}; + + +class Mars2GribCoreException : public eckit::Exception, public std::nested_exception { +public: + + Mars2GribCoreException(std::string reason, const eckit::CodeLocation& loc = eckit::CodeLocation()) : + eckit::Exception(reason, loc) {} + + virtual ~Mars2GribCoreException() = default; + + virtual void printFrame(std::ofstream& out, const std::string& pad) const { + + const auto& loc = location(); + + out << pad << "+ file: " << loc.file() << "\n" << pad << "+ function: " << loc.func() << "\n" << pad << "+ line: " << loc.line() << "\n" << pad << "+ link: " << loc.file() << ":" << loc.line() << "\n" @@ -118,14 +141,14 @@ class Mars2GribMatcherException : public Mars2GribGenericException { const std::string levtype() const { return levtype_.has_value() ? levtype_.value() : "undefined"; } const std::string param() const { return param_.has_value() ? param_.value() : "undefined"; } - void printFrame(const std::string& pad) const override { + void printFrame(std::ofstream& out, const std::string& pad) const override { - Mars2GribGenericException::printFrame(pad); + Mars2GribGenericException::printFrame(out, pad); if (param_) { - LOG_DEBUG_LIB(LibMetkit) << pad << "+ param: " << param() << std::endl; + out << pad << "+ param: " << param() << std::endl; } if (levtype_) { - LOG_DEBUG_LIB(LibMetkit) << pad << "+ levtype: " << levtype() << std::endl; + out << pad << "+ levtype: " << levtype() << std::endl; } }; @@ -232,13 +255,13 @@ class Mars2GribConceptException : public Mars2GribGenericException { const std::optional& stage() const { return stage_; } const std::optional& section() const { return section_; } - void printFrame(const std::string& pad) const override { + void printFrame(std::ofstream& out, const std::string& pad) const override { - Mars2GribGenericException::printFrame(pad); + Mars2GribGenericException::printFrame(out, pad); auto print_opt = [&](const char* k, const std::optional& v) { if (v) - LOG_DEBUG_LIB(LibMetkit) << pad << "+ " << k << ": " << *v << "\n"; + out << pad << "+ " << k << ": " << *v << "\n"; }; print_opt("concept", conceptName_); @@ -288,11 +311,11 @@ class Mars2GribEncoderException : public Mars2GribGenericException { const std::string& optDict_json() const { return optDict_json_; } const std::string& encoderCfg_json() const { return encoderCfg_json_; } - void printFrame(const std::string& pad) const override { + void printFrame(std::ofstream& out, const std::string& pad) const override { - Mars2GribGenericException::printFrame(pad); + Mars2GribGenericException::printFrame(out, pad); - LOG_DEBUG_LIB(LibMetkit) << pad << "+ marsDict: " << marsDict_json_ << "\n" + out << pad << "+ marsDict: " << marsDict_json_ << "\n" << pad << "+ parDict: " << parDict_json_ << "\n" << pad << "+ optDict: " << optDict_json_ << "\n" << pad << "+ encoderCfg: " << encoderCfg_json_ << "\n"; @@ -364,28 +387,28 @@ inline std::string indent(std::size_t level) { /// @param e Root exception /// @param level Indentation level /// @param frame Frame counter -inline void printExtendedStack(const std::exception& e, std::size_t level = 0, std::size_t frame = 1) { +inline void printExtendedStack(const std::exception& e, std::ofstream& out, std::size_t level = 0, std::size_t frame = 1) { const std::string pad = indent(level); - LOG_DEBUG_LIB(LibMetkit) << pad << "+ " << std::string(lineSize, '=') << std::endl + out << pad << "+ " << std::string(lineSize, '=') << std::endl << pad << "+ frame " << frame << std::endl << pad << "+ " << std::string(lineSize, '-') << std::endl; if (const auto* me = dynamic_cast(&e)) { - me->printFrame(pad); + me->printFrame(out, pad); } else { - LOG_DEBUG_LIB(LibMetkit) << pad << "+ message: " << e.what() << std::endl; + out << pad << "+ message: " << e.what() << std::endl; } - LOG_DEBUG_LIB(LibMetkit) << pad << "+ " << std::string(lineSize, '+') << std::endl; + out << pad << "+ " << std::string(lineSize, '+') << std::endl; try { std::rethrow_if_nested(e); } catch (const std::exception& nested) { - printExtendedStack(nested, level + 1, frame + 1); + printExtendedStack(nested, out, level + 1, frame + 1); } }