From 73f0f24ab023bdd87fac7b4ccf9bbe8b13cad772 Mon Sep 17 00:00:00 2001 From: zekageri Date: Tue, 10 Mar 2026 13:11:37 +0100 Subject: [PATCH] chore: align formatter baseline with esptoolkit-template --- .clang-format | 11 ++ .editorconfig | 11 ++ .vscode/bin/clang-format | 19 +++ .vscode/extensions.json | 9 ++ .vscode/settings.json | 30 ++++ .vscode/tasks.json | 12 ++ CONTRIBUTING.md | 12 +- README.md | 7 + examples/ClearAndReseed/ClearAndReseed.ino | 55 ++++---- examples/CodecAll/CodecAll.ino | 119 ++++++++-------- examples/Defaults/Defaults.ino | 45 +++--- examples/LocalDateTime/LocalDateTime.ino | 53 ++++---- examples/QuickStart/QuickStart.ino | 41 +++--- .../RuntimeOverrides/RuntimeOverrides.ino | 81 +++++------ scripts/format_cpp.sh | 24 ++++ src/esp_store/codec.h | 51 ++++--- src/esp_store/store.cpp | 23 ++-- src/esp_store/store.h | 24 +++- test/test_esp_store/test_esp_store.cpp | 128 +++++++++--------- 19 files changed, 462 insertions(+), 293 deletions(-) create mode 100644 .clang-format create mode 100644 .editorconfig create mode 100755 .vscode/bin/clang-format create mode 100644 .vscode/extensions.json create mode 100644 .vscode/settings.json create mode 100644 .vscode/tasks.json create mode 100755 scripts/format_cpp.sh diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..8450693 --- /dev/null +++ b/.clang-format @@ -0,0 +1,11 @@ +BasedOnStyle: LLVM +ColumnLimit: 100 +BinPackArguments: false +BinPackParameters: false +AllowAllArgumentsOnNextLine: false +AlignAfterOpenBracket: BlockIndent +UseTab: ForIndentation +IndentWidth: 4 +TabWidth: 4 +ContinuationIndentWidth: 4 +AllowShortFunctionsOnASingleLine: None diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..d89c76d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,11 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +charset = utf-8 + +[*.{c,cc,cpp,h,hpp,ino}] +indent_style = tab +indent_size = tab +tab_width = 4 diff --git a/.vscode/bin/clang-format b/.vscode/bin/clang-format new file mode 100755 index 0000000..0df371f --- /dev/null +++ b/.vscode/bin/clang-format @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if command -v clang-format >/dev/null 2>&1; then + exec clang-format "$@" +fi + +_home_dir="${HOME:-}" +if [ -n "$_home_dir" ]; then + _candidate="$(ls -1d "$_home_dir"/.vscode/extensions/ms-vscode.cpptools-*-linux-x64/LLVM/bin/clang-format 2>/dev/null | tail -n 1 || true)" + if [ -n "$_candidate" ] && [ -x "$_candidate" ]; then + exec "$_candidate" "$@" + fi +fi + +echo "clang-format executable not found." >&2 +echo "Install clang-format system-wide or install/update ms-vscode.cpptools." >&2 +exit 127 diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..f814711 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "pioarduino.pioarduino-ide", + "xaver.clang-format" + ], + "unwantedRecommendations": [ + "ms-vscode.cpptools-extension-pack" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..24368c8 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,30 @@ +{ + "files.associations": { + "*.ino": "cpp" + }, + "editor.defaultFormatter": "xaver.clang-format", + "C_Cpp.formatting": "Disabled", + "clang-format.style": "file", + "clang-format.executable": "${workspaceRoot}/.vscode/bin/clang-format", + "[cpp]": { + "editor.defaultFormatter": "xaver.clang-format", + "editor.detectIndentation": false, + "editor.insertSpaces": false, + "editor.tabSize": 4, + "editor.formatOnSave": true + }, + "[c]": { + "editor.defaultFormatter": "xaver.clang-format", + "editor.detectIndentation": false, + "editor.insertSpaces": false, + "editor.tabSize": 4, + "editor.formatOnSave": true + }, + "[arduino]": { + "editor.defaultFormatter": "xaver.clang-format", + "editor.detectIndentation": false, + "editor.insertSpaces": false, + "editor.tabSize": 4, + "editor.formatOnSave": true + } +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..20e66d5 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,12 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "Format Firmware Sources", + "type": "shell", + "command": "bash ${workspaceFolder}/scripts/format_cpp.sh", + "group": "build", + "problemMatcher": [] + } + ] +} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fc2883d..ebdcb1f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -134,19 +134,9 @@ Please keep these in mind when contributing: - **I/O**: Use `StreamUtils::WriteBufferingStream` for buffered writes. - **Validation**: Run schema hooks on create/update; on failure revert and return a validation error. - **Naming**: lowerCamelCase for methods/vars, UpperCamelCase for types, ALL_CAPS for simple constants/enums. -- **Formatting**: Use a consistent `clang-format` (LLVM/Google); keep lines ≤ 120 cols. +- **Formatting**: Follow the repository `.clang-format` + `.editorconfig` baseline from `esptoolkit-template` (LLVM-derived style, `ColumnLimit: 100`, tabs with width `4`, `BinPackArguments/Parameters: false`, `AllowShortFunctionsOnASingleLine: None`). - **Allocations**: Avoid hidden allocations in hot paths and inside event callbacks & sync loops. -Optional `.clang-format` starter (Google-like): -```yaml -BasedOnStyle: Google -IndentWidth: 4 -ColumnLimit: 120 -DerivePointerAlignment: false -PointerAlignment: Left -AllowShortFunctionsOnASingleLine: Empty -``` - --- ## Commit messages & branches diff --git a/README.md b/README.md index 65955de..d3332e9 100644 --- a/README.md +++ b/README.md @@ -152,6 +152,13 @@ if (ESPStoreCodec::decodeLocalDateTime(doc["localTime"], local)) { - `examples/ClearAndReseed` – clear the store and seed with defaults. - `examples/LocalDateTime` – store LocalDateTime as `{ epochSeconds, offsetMinutes }`. +## Formatting Baseline + +This repository follows the firmware formatting baseline from `esptoolkit-template`: +- `.clang-format` is the source of truth for C/C++/INO layout. +- `.editorconfig` enforces tabs (`tab_width = 4`), LF endings, and final newline. +- Format all tracked firmware sources with `bash scripts/format_cpp.sh`. + ## License MIT — see [LICENSE.md](LICENSE.md). diff --git a/examples/ClearAndReseed/ClearAndReseed.ino b/examples/ClearAndReseed/ClearAndReseed.ino index 8458d26..33ba7da 100644 --- a/examples/ClearAndReseed/ClearAndReseed.ino +++ b/examples/ClearAndReseed/ClearAndReseed.ino @@ -5,40 +5,43 @@ ESPJsonDB db; ESPStore store; void setup() { - Serial.begin(115200); + Serial.begin(115200); - if (!db.init("/db").ok()) { - Serial.println("DB init failed"); - return; - } + if (!db.init("/db").ok()) { + Serial.println("DB init failed"); + return; + } - store.init(&db, "systemConf"); + store.init(&db, "systemConf"); - JsonDocument cfg; - cfg["mode"] = "normal"; - cfg["retries"] = 3; + JsonDocument cfg; + cfg["mode"] = "normal"; + cfg["retries"] = 3; - auto st = store.set(cfg.as()); - Serial.printf("Seed set: %s\n", st.ok() ? "OK" : st.message); + auto st = store.set(cfg.as()); + Serial.printf("Seed set: %s\n", st.ok() ? "OK" : st.message); - st = store.syncNow(); - Serial.printf("Sync: %s\n", st.ok() ? "OK" : st.message); + st = store.syncNow(); + Serial.printf("Sync: %s\n", st.ok() ? "OK" : st.message); - st = store.clear(); - Serial.printf("Clear: %s\n", st.ok() ? "OK" : st.message); + st = store.clear(); + Serial.printf("Clear: %s\n", st.ok() ? "OK" : st.message); - JsonDocument fallback; - fallback["mode"] = "safe"; - fallback["retries"] = 1; + JsonDocument fallback; + fallback["mode"] = "safe"; + fallback["retries"] = 1; - bool usedDefault = false; - auto res = store.getOr(fallback.as(), &usedDefault); - Serial.printf("After clear getOr: %s (%s)\n", - res.ok() ? "OK" : res.message(), - usedDefault ? "default" : "stored"); + bool usedDefault = false; + auto res = store.getOr(fallback.as(), &usedDefault); + Serial.printf( + "After clear getOr: %s (%s)\n", + res.ok() ? "OK" : res.message(), + usedDefault ? "default" : "stored" + ); - serializeJsonPretty(res.data, Serial); - store.deinit(); + serializeJsonPretty(res.data, Serial); + store.deinit(); } -void loop() {} +void loop() { +} diff --git a/examples/CodecAll/CodecAll.ino b/examples/CodecAll/CodecAll.ino index b2e1762..5e977f4 100644 --- a/examples/CodecAll/CodecAll.ino +++ b/examples/CodecAll/CodecAll.ino @@ -1,69 +1,72 @@ +#include #include #include #include -#include ESPJsonDB db; ESPStore store; ESPDate date; void setup() { - Serial.begin(115200); - - if (!db.init("/db").ok()) { - Serial.println("DB init failed"); - return; - } - - store.init(&db, "codecDemo"); - - JsonDocument doc; - - IPAddress ip(192, 168, 1, 42); - ESPStoreCodec::encodeIpString(doc["ipString"], ip); - ESPStoreCodec::encodeIpArray(doc["ipArray"], ip); - - DateTime dt{}; - dt.epochSeconds = 1730000000; // fixed sample epoch - ESPStoreCodec::encodeDateTimeEpoch(doc["timeEpoch"], dt); - ESPStoreCodec::encodeDateTimeIso(doc["timeIso"], dt, date); - - LocalDateTime local = date.nowLocal(); - ESPStoreCodec::encodeLocalDateTime(doc["localTime"], local); - - auto st = store.set(doc.as()); - Serial.printf("Store set: %s\n", st.ok() ? "OK" : st.message); - - auto res = store.get(); - if (!res.ok()) { - Serial.printf("Store get failed: %s\n", res.message()); - return; - } - - IPAddress ipStr; - IPAddress ipArr; - ESPStoreCodec::decodeIpString(res.data["ipString"], ipStr); - ESPStoreCodec::decodeIpArray(res.data["ipArray"], ipArr); - - DateTime dtEpoch{}; - DateTime dtIso{}; - ESPStoreCodec::decodeDateTimeEpoch(res.data["timeEpoch"], dtEpoch); - ESPStoreCodec::decodeDateTimeIso(res.data["timeIso"], dtIso, date); - - LocalDateTime localDecoded{}; - bool localOk = ESPStoreCodec::decodeLocalDateTime(res.data["localTime"], localDecoded); - - Serial.printf("IP string: %s\n", ipStr.toString().c_str()); - Serial.printf("IP array: %s\n", ipArr.toString().c_str()); - Serial.printf("Epoch: %lld\n", static_cast(dtEpoch.epochSeconds)); - Serial.printf("ISO epoch: %lld\n", static_cast(dtIso.epochSeconds)); - if (localOk) { - Serial.printf("Local epoch: %lld offset: %d\n", - static_cast(localDecoded.utc.epochSeconds), - localDecoded.offsetMinutes); - } - - store.deinit(); + Serial.begin(115200); + + if (!db.init("/db").ok()) { + Serial.println("DB init failed"); + return; + } + + store.init(&db, "codecDemo"); + + JsonDocument doc; + + IPAddress ip(192, 168, 1, 42); + ESPStoreCodec::encodeIpString(doc["ipString"], ip); + ESPStoreCodec::encodeIpArray(doc["ipArray"], ip); + + DateTime dt{}; + dt.epochSeconds = 1730000000; // fixed sample epoch + ESPStoreCodec::encodeDateTimeEpoch(doc["timeEpoch"], dt); + ESPStoreCodec::encodeDateTimeIso(doc["timeIso"], dt, date); + + LocalDateTime local = date.nowLocal(); + ESPStoreCodec::encodeLocalDateTime(doc["localTime"], local); + + auto st = store.set(doc.as()); + Serial.printf("Store set: %s\n", st.ok() ? "OK" : st.message); + + auto res = store.get(); + if (!res.ok()) { + Serial.printf("Store get failed: %s\n", res.message()); + return; + } + + IPAddress ipStr; + IPAddress ipArr; + ESPStoreCodec::decodeIpString(res.data["ipString"], ipStr); + ESPStoreCodec::decodeIpArray(res.data["ipArray"], ipArr); + + DateTime dtEpoch{}; + DateTime dtIso{}; + ESPStoreCodec::decodeDateTimeEpoch(res.data["timeEpoch"], dtEpoch); + ESPStoreCodec::decodeDateTimeIso(res.data["timeIso"], dtIso, date); + + LocalDateTime localDecoded{}; + bool localOk = ESPStoreCodec::decodeLocalDateTime(res.data["localTime"], localDecoded); + + Serial.printf("IP string: %s\n", ipStr.toString().c_str()); + Serial.printf("IP array: %s\n", ipArr.toString().c_str()); + Serial.printf("Epoch: %lld\n", static_cast(dtEpoch.epochSeconds)); + Serial.printf("ISO epoch: %lld\n", static_cast(dtIso.epochSeconds)); + if (localOk) { + Serial.printf( + "Local epoch: %lld offset: %d\n", + static_cast(localDecoded.utc.epochSeconds), + localDecoded.offsetMinutes + ); + } + + store.deinit(); } -void loop() {} +void loop() { +} diff --git a/examples/Defaults/Defaults.ino b/examples/Defaults/Defaults.ino index 53d0153..12bda63 100644 --- a/examples/Defaults/Defaults.ino +++ b/examples/Defaults/Defaults.ino @@ -5,33 +5,34 @@ ESPJsonDB db; ESPStore netConf; void setup() { - Serial.begin(115200); + Serial.begin(115200); - JsonDocument defaults; - defaults["ssid"] = ""; - defaults["password"] = ""; - defaults["hostname"] = "ESP_DEVICE"; - defaults["autoReconnect"] = true; - netConf.setDefault(defaults.as()); + JsonDocument defaults; + defaults["ssid"] = ""; + defaults["password"] = ""; + defaults["hostname"] = "ESP_DEVICE"; + defaults["autoReconnect"] = true; + netConf.setDefault(defaults.as()); - if (!db.init("/db").ok()) { - Serial.println("DB init failed"); - return; - } + if (!db.init("/db").ok()) { + Serial.println("DB init failed"); + return; + } - netConf.init(&db, "netConf"); + netConf.init(&db, "netConf"); - bool usedDefault = false; - auto res = netConf.getOr(&usedDefault); + bool usedDefault = false; + auto res = netConf.getOr(&usedDefault); - if (!res.ok()) { - Serial.printf("Failed to read config: %s\n", res.message()); - return; - } + if (!res.ok()) { + Serial.printf("Failed to read config: %s\n", res.message()); + return; + } - Serial.printf("Config source: %s\n", usedDefault ? "default" : "stored"); - serializeJsonPretty(res.data, Serial); - netConf.deinit(); + Serial.printf("Config source: %s\n", usedDefault ? "default" : "stored"); + serializeJsonPretty(res.data, Serial); + netConf.deinit(); } -void loop() {} +void loop() { +} diff --git a/examples/LocalDateTime/LocalDateTime.ino b/examples/LocalDateTime/LocalDateTime.ino index 242e071..cf0c365 100644 --- a/examples/LocalDateTime/LocalDateTime.ino +++ b/examples/LocalDateTime/LocalDateTime.ino @@ -1,44 +1,45 @@ +#include #include #include #include -#include ESPJsonDB db; ESPStore store; ESPDate date; void setup() { - Serial.begin(115200); + Serial.begin(115200); - if (!db.init("/db").ok()) { - Serial.println("DB init failed"); - return; - } + if (!db.init("/db").ok()) { + Serial.println("DB init failed"); + return; + } - store.init(&db, "localTimeConf"); + store.init(&db, "localTimeConf"); - JsonDocument doc; - LocalDateTime nowLocal = date.nowLocal(); - ESPStoreCodec::encodeLocalDateTime(doc["localTime"], nowLocal); + JsonDocument doc; + LocalDateTime nowLocal = date.nowLocal(); + ESPStoreCodec::encodeLocalDateTime(doc["localTime"], nowLocal); - auto st = store.set(doc.as()); - Serial.printf("Store set: %s\n", st.ok() ? "OK" : st.message); + auto st = store.set(doc.as()); + Serial.printf("Store set: %s\n", st.ok() ? "OK" : st.message); - auto res = store.get(); - if (!res.ok()) { - Serial.printf("Store get failed: %s\n", res.message()); - return; - } + auto res = store.get(); + if (!res.ok()) { + Serial.printf("Store get failed: %s\n", res.message()); + return; + } - LocalDateTime loaded{}; - if (!ESPStoreCodec::decodeLocalDateTime(res.data["localTime"], loaded)) { - Serial.println("Failed to decode LocalDateTime"); - return; - } + LocalDateTime loaded{}; + if (!ESPStoreCodec::decodeLocalDateTime(res.data["localTime"], loaded)) { + Serial.println("Failed to decode LocalDateTime"); + return; + } - Serial.printf("Loaded epoch: %lld\n", static_cast(loaded.utc.epochSeconds)); - Serial.printf("Loaded offset minutes: %d\n", loaded.offsetMinutes); - store.deinit(); + Serial.printf("Loaded epoch: %lld\n", static_cast(loaded.utc.epochSeconds)); + Serial.printf("Loaded offset minutes: %d\n", loaded.offsetMinutes); + store.deinit(); } -void loop() {} +void loop() { +} diff --git a/examples/QuickStart/QuickStart.ino b/examples/QuickStart/QuickStart.ino index 1f084c0..27ddc9d 100644 --- a/examples/QuickStart/QuickStart.ino +++ b/examples/QuickStart/QuickStart.ino @@ -5,31 +5,32 @@ ESPJsonDB db; ESPStore store; void setup() { - Serial.begin(115200); + Serial.begin(115200); - if (!db.init("/db").ok()) { - Serial.println("DB init failed"); - return; - } + if (!db.init("/db").ok()) { + Serial.println("DB init failed"); + return; + } - store.init(&db, "netConf"); + store.init(&db, "netConf"); - JsonDocument cfg; - cfg["ssid"] = "MyWiFi"; - cfg["password"] = "supersecret"; - cfg["hostname"] = "ESP_DEVICE"; + JsonDocument cfg; + cfg["ssid"] = "MyWiFi"; + cfg["password"] = "supersecret"; + cfg["hostname"] = "ESP_DEVICE"; - auto st = store.set(cfg.as()); - Serial.printf("Store set: %s\n", st.ok() ? "OK" : st.message); + auto st = store.set(cfg.as()); + Serial.printf("Store set: %s\n", st.ok() ? "OK" : st.message); - auto res = store.get(); - if (!res.ok()) { - Serial.printf("Failed to load config: %s\n", res.message()); - return; - } + auto res = store.get(); + if (!res.ok()) { + Serial.printf("Failed to load config: %s\n", res.message()); + return; + } - serializeJsonPretty(res.data, Serial); - store.deinit(); + serializeJsonPretty(res.data, Serial); + store.deinit(); } -void loop() {} +void loop() { +} diff --git a/examples/RuntimeOverrides/RuntimeOverrides.ino b/examples/RuntimeOverrides/RuntimeOverrides.ino index 47d5486..a96f215 100644 --- a/examples/RuntimeOverrides/RuntimeOverrides.ino +++ b/examples/RuntimeOverrides/RuntimeOverrides.ino @@ -5,43 +5,48 @@ ESPJsonDB db; ESPStore netConf; void setup() { - Serial.begin(115200); - - JsonDocument defaults; - defaults["ssid"] = ""; - defaults["password"] = ""; - defaults["hostname"] = "ESP_DEVICE"; - netConf.setDefault(defaults.as()); - - if (!db.init("/db").ok()) { - Serial.println("DB init failed"); - return; - } - - netConf.init(&db, "netConf"); - - bool usedDefault = false; - auto res = netConf.getOr(&usedDefault); - Serial.printf("Initial getOr: %s (%s)\n", - res.ok() ? "OK" : res.message(), - usedDefault ? "default" : "stored"); - - JsonDocument runtime; - runtime["ssid"] = "RuntimeWiFi"; - runtime["password"] = "secret"; - runtime["hostname"] = "ESP_RUNTIME"; - - auto st = netConf.set(runtime.as()); - Serial.printf("Runtime set: %s\n", st.ok() ? "OK" : st.message); - - usedDefault = false; - auto res2 = netConf.getOr(&usedDefault); - Serial.printf("After set getOr: %s (%s)\n", - res2.ok() ? "OK" : res2.message(), - usedDefault ? "default" : "stored"); - - serializeJsonPretty(res2.data, Serial); - netConf.deinit(); + Serial.begin(115200); + + JsonDocument defaults; + defaults["ssid"] = ""; + defaults["password"] = ""; + defaults["hostname"] = "ESP_DEVICE"; + netConf.setDefault(defaults.as()); + + if (!db.init("/db").ok()) { + Serial.println("DB init failed"); + return; + } + + netConf.init(&db, "netConf"); + + bool usedDefault = false; + auto res = netConf.getOr(&usedDefault); + Serial.printf( + "Initial getOr: %s (%s)\n", + res.ok() ? "OK" : res.message(), + usedDefault ? "default" : "stored" + ); + + JsonDocument runtime; + runtime["ssid"] = "RuntimeWiFi"; + runtime["password"] = "secret"; + runtime["hostname"] = "ESP_RUNTIME"; + + auto st = netConf.set(runtime.as()); + Serial.printf("Runtime set: %s\n", st.ok() ? "OK" : st.message); + + usedDefault = false; + auto res2 = netConf.getOr(&usedDefault); + Serial.printf( + "After set getOr: %s (%s)\n", + res2.ok() ? "OK" : res2.message(), + usedDefault ? "default" : "stored" + ); + + serializeJsonPretty(res2.data, Serial); + netConf.deinit(); } -void loop() {} +void loop() { +} diff --git a/scripts/format_cpp.sh b/scripts/format_cpp.sh new file mode 100755 index 0000000..7d17b04 --- /dev/null +++ b/scripts/format_cpp.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash + +set -euo pipefail + +_repo_root="$(git rev-parse --show-toplevel 2>/dev/null || pwd)" +_clang_format="${_repo_root}/.vscode/bin/clang-format" + +if [ ! -x "${_clang_format}" ]; then + echo "clang-format wrapper not found: ${_clang_format}" >&2 + exit 1 +fi + +mapfile -d '' _format_files < <( + git -C "${_repo_root}" ls-files -z -- '*.c' '*.cc' '*.cpp' '*.h' '*.hpp' '*.ino' +) + +if [ "${#_format_files[@]}" -eq 0 ]; then + echo "No tracked C/C++/INO files found to format." + exit 0 +fi + +"${_clang_format}" -i --style=file "${_format_files[@]}" + +echo "Formatted ${#_format_files[@]} files." diff --git a/src/esp_store/codec.h b/src/esp_store/codec.h index 1318d63..4de53d0 100644 --- a/src/esp_store/codec.h +++ b/src/esp_store/codec.h @@ -34,7 +34,8 @@ class ESPStoreCodec { } static bool decodeIpString(JsonVariantConst src, IPAddress &out) { - if (src.isNull()) return false; + if (src.isNull()) + return false; const char *str = nullptr; if (src.is()) { str = src.as(); @@ -43,20 +44,25 @@ class ESPStoreCodec { } else { return false; } - if (!str || !*str) return false; + if (!str || !*str) + return false; return out.fromString(str); } static bool decodeIpArray(JsonVariantConst src, IPAddress &out) { - if (!src.is()) return false; + if (!src.is()) + return false; JsonArrayConst arr = src.as(); - if (arr.size() != 4) return false; + if (arr.size() != 4) + return false; uint8_t octets[4]; for (size_t i = 0; i < 4; ++i) { JsonVariantConst v = arr[i]; - if (!v.is() && !v.is()) return false; + if (!v.is() && !v.is()) + return false; int val = v.as(); - if (val < 0 || val > 255) return false; + if (val < 0 || val > 255) + return false; octets[i] = static_cast(val); } out = IPAddress(octets[0], octets[1], octets[2], octets[3]); @@ -64,7 +70,8 @@ class ESPStoreCodec { } static bool decodeIp(JsonVariantConst src, IPAddress &out) { - if (decodeIpString(src, out)) return true; + if (decodeIpString(src, out)) + return true; return decodeIpArray(src, out); } @@ -74,8 +81,10 @@ class ESPStoreCodec { } static bool decodeEpochSeconds(JsonVariantConst src, int64_t &out) { - if (src.isNull()) return false; - if (!(src.is() || src.is() || src.is())) return false; + if (src.isNull()) + return false; + if (!(src.is() || src.is() || src.is())) + return false; out = src.as(); return true; } @@ -87,20 +96,23 @@ class ESPStoreCodec { static bool decodeDateTimeEpoch(JsonVariantConst src, DateTime &out) { int64_t epoch = 0; - if (!decodeEpochSeconds(src, epoch)) return false; + if (!decodeEpochSeconds(src, epoch)) + return false; out.epochSeconds = epoch; return true; } static bool encodeDateTimeIso(JsonVariant dst, const DateTime &dt, ESPDate &date) { char buf[32]; - if (!date.formatUtc(dt, ESPDateFormat::Iso8601, buf, sizeof(buf))) return false; + if (!date.formatUtc(dt, ESPDateFormat::Iso8601, buf, sizeof(buf))) + return false; dst.set(buf); return true; } static bool decodeDateTimeIso(JsonVariantConst src, DateTime &out, ESPDate &date) { - if (src.isNull()) return false; + if (src.isNull()) + return false; const char *str = nullptr; if (src.is()) { str = src.as(); @@ -109,9 +121,11 @@ class ESPStoreCodec { } else { return false; } - if (!str || !*str) return false; + if (!str || !*str) + return false; auto parsed = date.parseIso8601Utc(str); - if (!parsed.ok) return false; + if (!parsed.ok) + return false; out = parsed.value; return true; } @@ -125,11 +139,14 @@ class ESPStoreCodec { } static bool decodeLocalDateTime(JsonVariantConst src, LocalDateTime &out) { - if (!src.is()) return false; + if (!src.is()) + return false; JsonObjectConst obj = src.as(); - if (!obj.containsKey("epochSeconds") || !obj.containsKey("offsetMinutes")) return false; + if (!obj.containsKey("epochSeconds") || !obj.containsKey("offsetMinutes")) + return false; int64_t epoch = 0; - if (!decodeEpochSeconds(obj["epochSeconds"], epoch)) return false; + if (!decodeEpochSeconds(obj["epochSeconds"], epoch)) + return false; out.utc.epochSeconds = epoch; out.offsetMinutes = obj["offsetMinutes"].as(); out.ok = true; diff --git a/src/esp_store/store.cpp b/src/esp_store/store.cpp index 5c671ef..194d365 100644 --- a/src/esp_store/store.cpp +++ b/src/esp_store/store.cpp @@ -65,7 +65,7 @@ DbStatus ESPStore::ensureReady() const { DbStatus ESPStore::registerSchema() { Schema s; s.fields = { - {"key", FieldType::String, nullptr, true}, + {"key", FieldType::String, nullptr, true}, }; return _db->registerSchema(_collection, s); } @@ -107,7 +107,8 @@ StoreResponse ESPStore::get() { StoreResponse ESPStore::getOr(bool *usedDefault) { if (!_hasDefault) { auto res = get(); - if (usedDefault) *usedDefault = false; + if (usedDefault) + *usedDefault = false; return res; } return getOr(_defaultDoc.as(), usedDefault); @@ -116,7 +117,8 @@ StoreResponse ESPStore::getOr(bool *usedDefault) { StoreResponse ESPStore::getOr(JsonVariantConst fallback, bool *usedDefault) { StoreResponse res = get(); if (res.ok()) { - if (usedDefault) *usedDefault = false; + if (usedDefault) + *usedDefault = false; return res; } @@ -124,17 +126,20 @@ StoreResponse ESPStore::getOr(JsonVariantConst fallback, bool *usedDefault) { res.data.clear(); res.data.set(fallback); res.setStatus({DbStatusCode::Ok, kMsgDefaultUsed}); - if (usedDefault) *usedDefault = true; + if (usedDefault) + *usedDefault = true; return res; } - if (usedDefault) *usedDefault = false; + if (usedDefault) + *usedDefault = false; return res; } DbStatus ESPStore::set(JsonVariantConst value) { auto ready = ensureReady(); - if (!ready.ok()) return ready; + if (!ready.ok()) + return ready; JsonDocument filter; filter["key"] = _key.c_str(); @@ -148,7 +153,8 @@ DbStatus ESPStore::set(JsonVariantConst value) { DbStatus ESPStore::clear() { auto ready = ensureReady(); - if (!ready.ok()) return ready; + if (!ready.ok()) + return ready; auto removed = _db->removeMany(_collection, [this](const DocView &doc) { return doc["key"].as() == _key; }); @@ -157,6 +163,7 @@ DbStatus ESPStore::clear() { DbStatus ESPStore::syncNow() { auto ready = ensureReady(); - if (!ready.ok()) return ready; + if (!ready.ok()) + return ready; return _db->syncNow(); } diff --git a/src/esp_store/store.h b/src/esp_store/store.h index b22919a..95df5aa 100644 --- a/src/esp_store/store.h +++ b/src/esp_store/store.h @@ -11,8 +11,12 @@ struct StoreResponse { JsonDocument data; const char *error = nullptr; - bool ok() const { return status.ok(); } - const char *message() const { return status.message; } + bool ok() const { + return status.ok(); + } + const char *message() const { + return status.message; + } void setStatus(const DbStatus &st) { status = st; @@ -23,14 +27,18 @@ struct StoreResponse { class ESPStore { public: ESPStore() = default; - ~ESPStore() { deinit(); } + ~ESPStore() { + deinit(); + } DbStatus init(ESPJsonDB *db, const char *collection); DbStatus init(ESPJsonDB *db, const String &collection); DbStatus init(ESPJsonDB *db, const char *collection, const char *key); DbStatus init(ESPJsonDB *db, const String &collection, const String &key); void deinit(); - bool isInitialized() const { return _initialized; } + bool isInitialized() const { + return _initialized; + } DbStatus setDefault(JsonVariantConst value); StoreResponse get(); @@ -41,8 +49,12 @@ class ESPStore { DbStatus clear(); DbStatus syncNow(); - const std::string &collection() const { return _collection; } - const std::string &key() const { return _key; } + const std::string &collection() const { + return _collection; + } + const std::string &key() const { + return _key; + } private: ESPJsonDB *_db = nullptr; diff --git a/test/test_esp_store/test_esp_store.cpp b/test/test_esp_store/test_esp_store.cpp index ebd2fbe..f4137b9 100644 --- a/test/test_esp_store/test_esp_store.cpp +++ b/test/test_esp_store/test_esp_store.cpp @@ -6,84 +6,90 @@ static constexpr const char *kTestDbPath = "/db_store_contract"; static void test_deinit_is_safe_before_init() { - ESPStore store; - TEST_ASSERT_FALSE(store.isInitialized()); + ESPStore store; + TEST_ASSERT_FALSE(store.isInitialized()); - store.deinit(); - store.deinit(); + store.deinit(); + store.deinit(); - TEST_ASSERT_FALSE(store.isInitialized()); + TEST_ASSERT_FALSE(store.isInitialized()); - auto res = store.get(); - TEST_ASSERT_FALSE(res.ok()); - TEST_ASSERT_EQUAL_UINT8(static_cast(DbStatusCode::InvalidArgument), - static_cast(res.status.code)); + auto res = store.get(); + TEST_ASSERT_FALSE(res.ok()); + TEST_ASSERT_EQUAL_UINT8( + static_cast(DbStatusCode::InvalidArgument), + static_cast(res.status.code) + ); } static void test_deinit_is_idempotent_after_init() { - ESPJsonDB db; - TEST_ASSERT_TRUE(db.init(kTestDbPath).ok()); + ESPJsonDB db; + TEST_ASSERT_TRUE(db.init(kTestDbPath).ok()); - ESPStore store; - TEST_ASSERT_TRUE(store.init(&db, "store_teardown_contract", "idempotent_key").ok()); - TEST_ASSERT_TRUE(store.isInitialized()); + ESPStore store; + TEST_ASSERT_TRUE(store.init(&db, "store_teardown_contract", "idempotent_key").ok()); + TEST_ASSERT_TRUE(store.isInitialized()); - store.deinit(); - TEST_ASSERT_FALSE(store.isInitialized()); + store.deinit(); + TEST_ASSERT_FALSE(store.isInitialized()); - store.deinit(); - TEST_ASSERT_FALSE(store.isInitialized()); + store.deinit(); + TEST_ASSERT_FALSE(store.isInitialized()); - auto res = store.get(); - TEST_ASSERT_FALSE(res.ok()); - TEST_ASSERT_EQUAL_UINT8(static_cast(DbStatusCode::InvalidArgument), - static_cast(res.status.code)); + auto res = store.get(); + TEST_ASSERT_FALSE(res.ok()); + TEST_ASSERT_EQUAL_UINT8( + static_cast(DbStatusCode::InvalidArgument), + static_cast(res.status.code) + ); - db.deinit(); + db.deinit(); } static void test_init_deinit_init_lifecycle() { - ESPJsonDB db; - TEST_ASSERT_TRUE(db.init(kTestDbPath).ok()); + ESPJsonDB db; + TEST_ASSERT_TRUE(db.init(kTestDbPath).ok()); - ESPStore store; + ESPStore store; - JsonDocument defaults; - defaults["mode"] = "safe"; - TEST_ASSERT_TRUE(store.setDefault(defaults.as()).ok()); + JsonDocument defaults; + defaults["mode"] = "safe"; + TEST_ASSERT_TRUE(store.setDefault(defaults.as()).ok()); - TEST_ASSERT_TRUE(store.init(&db, "store_teardown_contract", "first_key").ok()); - TEST_ASSERT_TRUE(store.clear().ok()); + TEST_ASSERT_TRUE(store.init(&db, "store_teardown_contract", "first_key").ok()); + TEST_ASSERT_TRUE(store.clear().ok()); - bool usedDefault = false; - auto first = store.getOr(&usedDefault); - TEST_ASSERT_TRUE(first.ok()); - TEST_ASSERT_TRUE(usedDefault); + bool usedDefault = false; + auto first = store.getOr(&usedDefault); + TEST_ASSERT_TRUE(first.ok()); + TEST_ASSERT_TRUE(usedDefault); - store.deinit(); - TEST_ASSERT_FALSE(store.isInitialized()); + store.deinit(); + TEST_ASSERT_FALSE(store.isInitialized()); - TEST_ASSERT_TRUE(store.init(&db, "store_teardown_contract", "second_key").ok()); - TEST_ASSERT_TRUE(store.isInitialized()); - TEST_ASSERT_TRUE(store.clear().ok()); + TEST_ASSERT_TRUE(store.init(&db, "store_teardown_contract", "second_key").ok()); + TEST_ASSERT_TRUE(store.isInitialized()); + TEST_ASSERT_TRUE(store.clear().ok()); - usedDefault = false; - auto withoutDefault = store.getOr(&usedDefault); - TEST_ASSERT_FALSE(withoutDefault.ok()); - TEST_ASSERT_FALSE(usedDefault); - TEST_ASSERT_EQUAL_UINT8(static_cast(DbStatusCode::NotFound), - static_cast(withoutDefault.status.code)); + usedDefault = false; + auto withoutDefault = store.getOr(&usedDefault); + TEST_ASSERT_FALSE(withoutDefault.ok()); + TEST_ASSERT_FALSE(usedDefault); + TEST_ASSERT_EQUAL_UINT8( + static_cast(DbStatusCode::NotFound), + static_cast(withoutDefault.status.code) + ); - JsonDocument runtime; - runtime["retries"] = 3; - TEST_ASSERT_TRUE(store.set(runtime.as()).ok()); + JsonDocument runtime; + runtime["retries"] = 3; + TEST_ASSERT_TRUE(store.set(runtime.as()).ok()); - auto loaded = store.get(); - TEST_ASSERT_TRUE(loaded.ok()); - TEST_ASSERT_EQUAL_INT(3, loaded.data["retries"] | 0); + auto loaded = store.get(); + TEST_ASSERT_TRUE(loaded.ok()); + TEST_ASSERT_EQUAL_INT(3, loaded.data["retries"] | 0); - store.deinit(); - db.deinit(); + store.deinit(); + db.deinit(); } void setUp() { @@ -93,14 +99,14 @@ void tearDown() { } void setup() { - delay(2000); - UNITY_BEGIN(); - RUN_TEST(test_deinit_is_safe_before_init); - RUN_TEST(test_deinit_is_idempotent_after_init); - RUN_TEST(test_init_deinit_init_lifecycle); - UNITY_END(); + delay(2000); + UNITY_BEGIN(); + RUN_TEST(test_deinit_is_safe_before_init); + RUN_TEST(test_deinit_is_idempotent_after_init); + RUN_TEST(test_init_deinit_init_lifecycle); + UNITY_END(); } void loop() { - vTaskDelay(pdMS_TO_TICKS(1000)); + vTaskDelay(pdMS_TO_TICKS(1000)); }