From 7ce7c7c8949ca35982834c3ee210f299d4c8938f Mon Sep 17 00:00:00 2001 From: Steven Atkinson Date: Sat, 25 Apr 2026 17:17:31 -0700 Subject: [PATCH 1/3] [FEATURE] Add extensible model version support registry Introduce a pluggable version-support checker registry and route config version validation through it, so downstream projects can register scoped version policies without editing get_dsp directly. Made-with: Cursor --- NAM/get_dsp.cpp | 87 ++++++++++++++++++++++++++++++++++++++++--------- NAM/get_dsp.h | 21 ++++++++++++ 2 files changed, 92 insertions(+), 16 deletions(-) diff --git a/NAM/get_dsp.cpp b/NAM/get_dsp.cpp index 73dd3ef0..d9c9edef 100644 --- a/NAM/get_dsp.cpp +++ b/NAM/get_dsp.cpp @@ -1,5 +1,7 @@ #include #include +#include +#include #include #include @@ -11,6 +13,49 @@ namespace nam { +namespace +{ + +class CoreVersionSupportChecker : public IVersionSupportChecker +{ +public: + bool matches(const std::string& version) const override + { + static const std::regex semver_regex(R"(^\d+\.\d+\.\d+$)"); + return std::regex_match(version, semver_regex); + } + + Supported support(const std::string& version) const override + { + const Version parsed = ParseVersion(version); + const Version latest = ParseVersion(LATEST_FULLY_SUPPORTED_NAM_FILE_VERSION); + const Version earliest = ParseVersion(EARLIEST_SUPPORTED_NAM_FILE_VERSION); + + if (parsed < earliest) + return Supported::NO; + if (parsed.major > latest.major || parsed.minor > latest.minor) + return Supported::NO; + if (latest < parsed) + return Supported::PARTIAL; + return Supported::YES; + } +}; + +std::vector>& version_support_registry() +{ + static std::vector> registry{ + std::make_shared()}; + return registry; +} + +std::mutex& version_support_registry_mutex() +{ + static std::mutex registry_mutex; + return registry_mutex; +} + +} // namespace + Version ParseVersion(const std::string& versionStr) { // Split the version string into major, minor, and patch components @@ -47,32 +92,42 @@ Version ParseVersion(const std::string& versionStr) return Version(major, minor, patch); } -void verify_config_version(const std::string versionStr) +void register_version_support_checker(std::shared_ptr checker) { - Version version = ParseVersion(versionStr); - Version currentVersion = ParseVersion(LATEST_FULLY_SUPPORTED_NAM_FILE_VERSION); - Version earliestSupportedVersion = ParseVersion(EARLIEST_SUPPORTED_NAM_FILE_VERSION); + if (!checker) + throw std::invalid_argument("version support checker cannot be null"); + std::lock_guard lock(version_support_registry_mutex()); + version_support_registry().push_back(std::move(checker)); +} - if (version < earliestSupportedVersion) +Supported is_version_supported(const std::string version) +{ + std::lock_guard lock(version_support_registry_mutex()); + Supported best_support = Supported::NO; + for (const auto& checker : version_support_registry()) { - std::stringstream ss; - ss << "Model config is an unsupported version " << versionStr << ". The earliest supported version is " - << earliestSupportedVersion.toString() - << ". Try either converting the model to a more recent version, or " - "update your version of the NAM plugin."; - throw std::runtime_error(ss.str()); + if (!checker->matches(version)) + continue; + const auto candidate_support = checker->support(version); + if (static_cast(candidate_support) > static_cast(best_support)) + best_support = candidate_support; } - if (version.major > currentVersion.major || version.minor > currentVersion.minor) + return best_support; +} + +void verify_config_version(const std::string versionStr) +{ + const Supported support = is_version_supported(versionStr); + if (support == Supported::NO) { std::stringstream ss; - ss << "Model config is an unsupported version " << versionStr << ". The latest fully-supported version is " - << currentVersion.toString(); + ss << "Model config is an unsupported version " << versionStr << "."; throw std::runtime_error(ss.str()); } - else if (currentVersion < version) + if (support == Supported::PARTIAL) { + std::stringstream ss; std::cerr << "Model config is a partially-supported version " << versionStr - << ". The latest fully-supported version is " << currentVersion.toString() << ". Continuing with partial support." << std::endl; } } diff --git a/NAM/get_dsp.h b/NAM/get_dsp.h index 29fb2ec9..ddd3531f 100644 --- a/NAM/get_dsp.h +++ b/NAM/get_dsp.h @@ -1,11 +1,28 @@ #pragma once #include +#include +#include #include "dsp.h" namespace nam { +enum class Supported +{ + NO = 0, + PARTIAL = 1, + YES = 2 +}; + +class IVersionSupportChecker +{ +public: + virtual ~IVersionSupportChecker() = default; + virtual bool matches(const std::string& version) const = 0; + virtual Supported support(const std::string& version) const = 0; +}; + class Version { public: @@ -40,6 +57,10 @@ class Version Version ParseVersion(const std::string& versionStr); +void register_version_support_checker(std::shared_ptr checker); + +Supported is_version_supported(const std::string version); + void verify_config_version(const std::string versionStr); const std::string LATEST_FULLY_SUPPORTED_NAM_FILE_VERSION = "0.7.0"; From a4354f9a88aa997a5c41aa06b7b54d1d4cd7db64 Mon Sep 17 00:00:00 2001 From: Steven Atkinson Date: Sat, 25 Apr 2026 17:24:32 -0700 Subject: [PATCH 2/3] [REFACTOR] Remove version checker matches() API Simplify the version support checker contract to a single support(version) call, with checker-specific pattern filtering handled internally by returning NO for non-applicable formats. Made-with: Cursor --- NAM/get_dsp.cpp | 10 +++------- NAM/get_dsp.h | 1 - 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/NAM/get_dsp.cpp b/NAM/get_dsp.cpp index d9c9edef..423e8142 100644 --- a/NAM/get_dsp.cpp +++ b/NAM/get_dsp.cpp @@ -19,14 +19,12 @@ namespace class CoreVersionSupportChecker : public IVersionSupportChecker { public: - bool matches(const std::string& version) const override + Supported support(const std::string& version) const override { static const std::regex semver_regex(R"(^\d+\.\d+\.\d+$)"); - return std::regex_match(version, semver_regex); - } + if (!std::regex_match(version, semver_regex)) + return Supported::NO; - Supported support(const std::string& version) const override - { const Version parsed = ParseVersion(version); const Version latest = ParseVersion(LATEST_FULLY_SUPPORTED_NAM_FILE_VERSION); const Version earliest = ParseVersion(EARLIEST_SUPPORTED_NAM_FILE_VERSION); @@ -106,8 +104,6 @@ Supported is_version_supported(const std::string version) Supported best_support = Supported::NO; for (const auto& checker : version_support_registry()) { - if (!checker->matches(version)) - continue; const auto candidate_support = checker->support(version); if (static_cast(candidate_support) > static_cast(best_support)) best_support = candidate_support; diff --git a/NAM/get_dsp.h b/NAM/get_dsp.h index ddd3531f..da874fe9 100644 --- a/NAM/get_dsp.h +++ b/NAM/get_dsp.h @@ -19,7 +19,6 @@ class IVersionSupportChecker { public: virtual ~IVersionSupportChecker() = default; - virtual bool matches(const std::string& version) const = 0; virtual Supported support(const std::string& version) const = 0; }; From 4c379d2843067cdb654d9edbcbce6c4a89265da0 Mon Sep 17 00:00:00 2001 From: Steven Atkinson Date: Sat, 25 Apr 2026 17:34:53 -0700 Subject: [PATCH 3/3] [TEST] Add version support coverage for get_dsp Add tests for core version support classification and custom checker registration so the extensible version support path is validated end-to-end in the test runner. Made-with: Cursor --- tools/run_tests.cpp | 2 ++ tools/test/test_get_dsp.cpp | 44 +++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/tools/run_tests.cpp b/tools/run_tests.cpp index 61223a18..a4cfc4f8 100644 --- a/tools/run_tests.cpp +++ b/tools/run_tests.cpp @@ -273,6 +273,8 @@ int main() test_get_dsp::test_version_patch_one_beyond_supported(); test_get_dsp::test_version_minor_one_beyond_supported(); test_get_dsp::test_version_too_early(); + test_get_dsp::test_is_version_supported_core_behavior(); + test_get_dsp::test_register_custom_version_support_checker(); // Finally, some end-to-end tests. test_get_dsp::test_load_and_process_nam_files(); diff --git a/tools/test/test_get_dsp.cpp b/tools/test/test_get_dsp.cpp index 7d3be4a9..36aee83d 100644 --- a/tools/test/test_get_dsp.cpp +++ b/tools/test/test_get_dsp.cpp @@ -169,6 +169,24 @@ std::string createConfigWithVersion(const std::string& version) return j.dump(); } +class DemoVersionSupportChecker : public nam::IVersionSupportChecker +{ +public: + nam::Supported support(const std::string& version) const override + { + const std::string prefix = "DEMO::"; + if (version.rfind(prefix, 0) != 0) + return nam::Supported::NO; + + const std::string scopedVersion = version.substr(prefix.size()); + if (scopedVersion == "1.0.0") + return nam::Supported::YES; + if (scopedVersion.rfind("1.0.", 0) == 0) + return nam::Supported::PARTIAL; + return nam::Supported::NO; + } +}; + void test_version_patch_one_beyond_supported() { // Test that a .nam file with version one patch beyond the latest fully supported @@ -232,4 +250,30 @@ void test_version_too_early() } assert(threw); } + +void test_is_version_supported_core_behavior() +{ + assert(nam::is_version_supported(nam::LATEST_FULLY_SUPPORTED_NAM_FILE_VERSION) + == nam::Supported::YES); + + nam::Version patchBeyondLatest = nam::ParseVersion(nam::LATEST_FULLY_SUPPORTED_NAM_FILE_VERSION); + patchBeyondLatest.patch++; + assert(nam::is_version_supported(patchBeyondLatest.toString()) + == nam::Supported::PARTIAL); + + nam::Version minorBeyondLatest = nam::ParseVersion(nam::LATEST_FULLY_SUPPORTED_NAM_FILE_VERSION); + minorBeyondLatest.minor++; + minorBeyondLatest.patch = 0; + assert(nam::is_version_supported(minorBeyondLatest.toString()) + == nam::Supported::NO); +} + +void test_register_custom_version_support_checker() +{ + nam::register_version_support_checker(std::make_shared()); + + assert(nam::is_version_supported("DEMO::1.0.0") == nam::Supported::YES); + assert(nam::is_version_supported("DEMO::1.0.3") == nam::Supported::PARTIAL); + assert(nam::is_version_supported("DEMO::2.0.0") == nam::Supported::NO); +} }; // namespace test_get_dsp \ No newline at end of file