From 0c83bffe4771ea9621a98a5a333fc25d320c6296 Mon Sep 17 00:00:00 2001
From: William Emfinger <waemfinger@gmail.com>
Date: Tue, 26 Nov 2024 15:51:43 -0600
Subject: [PATCH] feat(nvs): Add erase key and erase namespace methods (#343)

* Update `espp::NvsHandle` to support `erase(std::error_code&)` and `erase(std::string_view key, std::error_code&)` methods
* Update `espp::Nvs` to support `erase(namespace, key)`, and `erase(namespace)` methods
* Update example to test new methods
---
 components/nvs/example/main/nvs_example.cpp | 39 ++++++++++++++++-
 components/nvs/include/nvs.hpp              | 31 +++++++++++++-
 components/nvs/include/nvs_errc.hpp         |  8 ++++
 components/nvs/include/nvs_handle_espp.hpp  | 46 +++++++++++++++++++++
 4 files changed, 122 insertions(+), 2 deletions(-)

diff --git a/components/nvs/example/main/nvs_example.cpp b/components/nvs/example/main/nvs_example.cpp
index 0d4a0889f..1b9d752ed 100644
--- a/components/nvs/example/main/nvs_example.cpp
+++ b/components/nvs/example/main/nvs_example.cpp
@@ -60,7 +60,7 @@ extern "C" void app_main(void) {
     if (ec) {
       fmt::print("Error: {}\n", ec.message());
     } else {
-      fmt::print("String = {}\n", str);
+      fmt::print("String = '{}'\n", str);
     }
     ec.clear();
 
@@ -74,9 +74,46 @@ extern "C" void app_main(void) {
     }
     ec.clear();
 
+    // test getting the string value again
+    nvs.get_var("system", "string", str, ec);
+    if (ec) {
+      fmt::print("Error: {}\n", ec.message());
+    } else {
+      fmt::print("String = '{}'\n", str);
+    }
+    ec.clear();
+
+    // test erasing the string value
+    nvs.erase("system", "string", ec);
+    if (ec) {
+      fmt::print("Error: {}\n", ec.message());
+    } else {
+      fmt::print("String erased\n");
+    }
+    ec.clear();
+
+    // now test getting it again to ensure it was erased
+    nvs.get_var("system", "string", str, ec);
+    if (ec) {
+      fmt::print("Sucess, got expected error when reading erased value: {}\n", ec.message());
+    } else {
+      fmt::print("Failure, got unexpected success when reading erased value\n");
+    }
+    ec.clear();
+
     counter++;
 
     if (counter > 10) {
+      // test erasing the whole namespace
+      nvs.erase("system", ec);
+      if (ec) {
+        fmt::print("Error: {}\n", ec.message());
+      } else {
+        fmt::print("Namespace erased\n");
+      }
+      ec.clear();
+
+      // now erase the wole nvs partition
       nvs.erase(ec);
       nvs.init(ec);
       counter = 0;
diff --git a/components/nvs/include/nvs.hpp b/components/nvs/include/nvs.hpp
index 5baf9432c..5ab2bf1a6 100644
--- a/components/nvs/include/nvs.hpp
+++ b/components/nvs/include/nvs.hpp
@@ -55,6 +55,35 @@ class Nvs : public BaseComponent {
     }
   }
 
+  /// @brief Erase a namespace from the NVS
+  /// @param[in] ns_name Namespace of the variable to erase
+  /// @param[out] ec Saves a std::error_code representing success or failure
+  bool erase(std::string_view ns_name, std::error_code &ec) {
+    NvsHandle handle(ns_name.data(), ec);
+    if (ec)
+      return false;
+
+    if (!handle.erase(ec))
+      return false;
+
+    return true;
+  }
+
+  /// @brief Erase a key from the NVS
+  /// @param[in] ns_name Namespace of the variable to erase
+  /// @param[in] key NVS Key of the variable to erase
+  /// @param[out] ec Saves a std::error_code representing success or failure
+  bool erase(std::string_view ns_name, std::string_view key, std::error_code &ec) {
+    NvsHandle handle(ns_name.data(), ec);
+    if (ec)
+      return false;
+
+    if (!handle.erase(key, ec))
+      return false;
+
+    return true;
+  }
+
   /// @brief Save a variable in the NVS and commit
   /// @param[in] ns_name Namespace of the variable to save
   /// @param[in] key NVS Key of the variable to save
@@ -84,7 +113,7 @@ class Nvs : public BaseComponent {
   template <typename T>
   void get_or_set_var(std::string_view ns_name, std::string_view key, T &value, T default_value,
                       std::error_code &ec) {
-    get_var(ns_name.data(), key.data(), value, ec);
+    get_or_set_var(ns_name.data(), key.data(), value, default_value, ec);
   }
 
   /// @brief Save a variable in the NVS and commit
diff --git a/components/nvs/include/nvs_errc.hpp b/components/nvs/include/nvs_errc.hpp
index 23932a133..629faf8a6 100644
--- a/components/nvs/include/nvs_errc.hpp
+++ b/components/nvs/include/nvs_errc.hpp
@@ -19,6 +19,8 @@ enum class NvsErrc {
   Init_NVS_Failed,
   Erase_NVS_Failed,
   Handle_Uninitialized,
+  Erase_NVS_Key_Failed,
+  Erase_NVS_Namespace_Failed,
 };
 
 struct NvsErrCategory : std::error_category {
@@ -60,6 +62,12 @@ std::string NvsErrCategory::message(int ev) const {
   case NvsErrc::Handle_Uninitialized:
     return "Handle not initialized";
 
+  case NvsErrc::Erase_NVS_Key_Failed:
+    return "Failed to erase NVS key";
+
+  case NvsErrc::Erase_NVS_Namespace_Failed:
+    return "Failed to erase NVS namespace";
+
   default:
     return "(unrecognized error)";
   }
diff --git a/components/nvs/include/nvs_handle_espp.hpp b/components/nvs/include/nvs_handle_espp.hpp
index 04d92cb30..40d1958a7 100644
--- a/components/nvs/include/nvs_handle_espp.hpp
+++ b/components/nvs/include/nvs_handle_espp.hpp
@@ -337,6 +337,52 @@ class NvsHandle : public BaseComponent {
     return;
   }
 
+  /// @brief Erase a key from the NVS
+  /// @param[in] key NVS Key to erase
+  /// @param[out] ec Saves a std::error_code representing success or failure
+  /// @return true if successful, false otherwise
+  bool erase(std::string_view key, std::error_code &ec) { return erase(key.data(), ec); }
+
+  /// @brief Erase a key from the NVS
+  /// @param[in] key NVS Key to erase
+  /// @param[out] ec Saves a std::error_code representing success or failure
+  /// @return true if successful, false otherwise
+  bool erase(const char *key, std::error_code &ec) {
+    if (!handle_) {
+      logger_.error("NVS Handle not initialized!");
+      return false;
+    }
+
+    if (!check_key(key, ec))
+      return false;
+
+    esp_err_t err = handle_->erase_item(key);
+    if (err != ESP_OK) {
+      logger_.error("Error {} erasing key '{}' from NVS!", esp_err_to_name(err), key);
+      ec = make_error_code(NvsErrc::Erase_NVS_Key_Failed);
+      return false;
+    }
+    return true;
+  }
+
+  /// @brief Erase all keys from the NVS associated with the namespace / handle
+  /// @param[out] ec Saves a std::error_code representing success or failure
+  /// @return true if successful, false otherwise
+  bool erase(std::error_code &ec) {
+    if (!handle_) {
+      logger_.error("NVS Handle not initialized!");
+      return false;
+    }
+
+    esp_err_t err = handle_->erase_all();
+    if (err != ESP_OK) {
+      logger_.error("Error {} erasing all keys from NVS!", esp_err_to_name(err));
+      ec = make_error_code(NvsErrc::Erase_NVS_Namespace_Failed);
+      return false;
+    }
+    return true;
+  }
+
 protected:
   std::unique_ptr<nvs::NVSHandle> handle_;