diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
index 306fffb5..d3ecbdbf 100644
--- a/.devcontainer/devcontainer.json
+++ b/.devcontainer/devcontainer.json
@@ -1,6 +1,6 @@
{
"name": "eclipse-s-core",
- "image": "ghcr.io/eclipse-score/devcontainer:latest",
+ "image": "ghcr.io/eclipse-score/devcontainer:1.0.0",
"features": {
"ghcr.io/devcontainers/features/rust:1.5.0": {
"version": "1.83.0",
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 78a9fa91..3b774d94 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -10,7 +10,6 @@
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************
-
name: QNX Rust Build Test
on: workflow_dispatch
diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml
deleted file mode 100644
index 224c63e1..00000000
--- a/.github/workflows/check.yml
+++ /dev/null
@@ -1,188 +0,0 @@
-# *******************************************************************************
-# Copyright (c) 2025 Contributors to the Eclipse Foundation
-#
-# See the NOTICE file(s) distributed with this work for additional
-# information regarding copyright ownership.
-#
-# This program and the accompanying materials are made available under the
-# terms of the Apache License Version 2.0 which is available at
-# https://www.apache.org/licenses/LICENSE-2.0
-#
-# SPDX-License-Identifier: Apache-2.0
-# *******************************************************************************
-
-name: Check compilation and code
-
-on:
- pull_request:
- types: [opened, reopened, synchronize]
- push:
- branches:
- - main
- merge_group:
- types: [checks_requested]
-
-defaults:
- run:
- shell: bash
-
-jobs:
- cargo-build:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - uses: dtolnay/rust-toolchain@stable
- with:
- toolchain: 1.83.0
- components: clippy, rustfmt
-
- - name: Cargo Clippy
- run: |
- cargo clippy --all-targets --all-features
-
- - name: Cargo Fmt
- run: |
- cargo fmt --check
-
- - name: Cargo Build
- run: |
- cargo build
-
- cargo-miri:
- runs-on: ubuntu-latest
- env:
- MIRIFLAGS: "-Zmiri-disable-isolation"
- steps:
- - uses: actions/checkout@v4
-
- - uses: dtolnay/rust-toolchain@stable
- with:
- toolchain: nightly-2024-12-17
- components: miri, rust-src
-
- - name: Install Clang
- run: |
- sudo apt update
- sudo apt install -y clang
-
- - name: Cargo Miri
- run: |
- cargo +nightly-2024-12-17 miri test
-
- cargo-coverage:
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v4
-
- - uses: dtolnay/rust-toolchain@stable
- with:
- toolchain: nightly-2024-12-17
- components: llvm-tools-preview, rust-src
-
- - name: Install cargo-llvm-cov
- run: |
- cargo +nightly-2024-12-17 install cargo-llvm-cov --locked
-
- - name: Cargo llvm-cov
- run: |
- cargo +nightly-2024-12-17 llvm-cov --branch --tests --lib
-
- rust-bazel:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
-
- - name: Setup Bazel with shared caching
- uses: bazel-contrib/setup-bazel@0.14.0
- with:
- disk-cache: true
- repository-cache: true
- bazelisk-cache: true
-
- - name: Bazel Build
- run: |
- bazel build \
- //src/rust/rust_kvs:rust_kvs \
- //src/rust/rust_kvs_tool:kvs_tool
-
- - name: Bazel Unit Tests
- run: |
- bazel test //src/rust/rust_kvs:tests
-
- - name: Bazel Unit Test with Coverage
- run: |
- bazel coverage //src/rust/rust_kvs:tests \
- --collect_code_coverage \
- --combined_report=lcov \
- --experimental_generate_llvm_lcov \
- --nocache_test_results \
- --nostamp
-
- - name: Install lcov
- run: |
- sudo apt-get update
- sudo apt-get install -y lcov
-
- - name: Extract Coverage for Rust Files
- run: |
- REPORT=$(find "$(bazel info output_path)" -type f -path '*/rust/*/coverage.dat' | head -n1)
- lcov \
- --rc branch_coverage=1 \
- --extract "$REPORT" '*.rs' -o "${REPORT}.rs" \
- --output-file kvs_coverage.info
-
- cpp-bazel:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
-
- - name: Setup Bazel with shared caching
- uses: bazel-contrib/setup-bazel@0.14.0
- with:
- disk-cache: true
- repository-cache: true
- bazelisk-cache: true
-
- - name: Bazel Build
- run: bazel build //:kvs_cpp
-
- - name: Bazel Unit Test with Coverage
- run: |
- bazel coverage //:test_kvs_cpp \
- --collect_code_coverage \
- --combined_report=lcov \
- --experimental_generate_llvm_lcov \
- --nocache_test_results \
- --nostamp
-
- - name: Install lcov
- run: |
- sudo apt-get update
- sudo apt-get install -y lcov
-
- - name: Extract Coverage for CPP Files
- run: |
- # ToDo: should work out of the box with bazel coverage (https://github.com/eclipse-score/toolchains_gcc/issues/21)
- # run test binary to generate coverage data (*.gcda files). Manually
- # since it is build already with the correct flags and the gcda ends
- # up in a proper location
- ./bazel-bin/src/cpp/tests/test_kvs_cpp
-
- # define some variables for easier usage
- BASE_DIR="$(pwd)"
- GCOV_TOOL="./bazel-persistency/external/score_toolchains_gcc++gcc+gcc_toolchain_gcc/bin/x86_64-unknown-linux-gnu-gcov"
- BIN_DIR="./bazel-bin/src/cpp/"
- OUT_FILE="lcov_coverage.info"
- # capture coverage info
- lcov --capture --directory "$BIN_DIR" --output-file "$OUT_FILE" --gcov-tool "$GCOV_TOOL" --base-directory "$BASE_DIR" --branch-coverage --ignore-errors mismatch --exclude "*/external/*"
- # display summary
- lcov --summary --rc branch_coverage=1 "$OUT_FILE"
- # generate html report (for local inspection if needed)
- # genhtml "$OUT_FILE" -o coverage_html --show-details --legend --function-coverage --branch-coverage
- rm ${OUT_FILE}
-
- - name: Bazel Benchmark
- run: bazel run -c opt //:bm_kvs_cpp
diff --git a/.github/workflows/component_integration_tests.yml b/.github/workflows/component_integration_tests.yml
index 95af6bdb..267e9339 100644
--- a/.github/workflows/component_integration_tests.yml
+++ b/.github/workflows/component_integration_tests.yml
@@ -10,7 +10,6 @@
#
# SPDX-License-Identifier: Apache-2.0
# *******************************************************************************
-
name: Component Integration Tests
on:
diff --git a/.gitignore b/.gitignore
index 547c1f7b..76fb540d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -42,6 +42,7 @@ user.bazelrc
# docs:incremental and docs:ide_support build artifacts
/_build
+docs/ubproject.toml
# Vale - editorial style guide
.vale.ini
diff --git a/BUILD b/BUILD
index 87721028..d40425e5 100644
--- a/BUILD
+++ b/BUILD
@@ -33,7 +33,12 @@ setup_starpls(
copyright_checker(
name = "copyright",
srcs = [
+ ".github",
+ "docs",
+ "examples",
"src",
+ "tests",
+ "tools",
"//:BUILD",
"//:MODULE.bazel",
],
diff --git a/MODULE.bazel b/MODULE.bazel
index de1a690b..8bff2deb 100644
--- a/MODULE.bazel
+++ b/MODULE.bazel
@@ -12,7 +12,7 @@
# *******************************************************************************
module(
name = "score_persistency",
- version = "0.2.0",
+ version = "0.2.1",
compatibility_level = 0,
)
@@ -72,20 +72,27 @@ bazel_dep(name = "googletest", version = "1.17.0.bcr.1")
bazel_dep(name = "google_benchmark", version = "1.9.4", dev_dependency = True)
## S-CORE bazel registry
-bazel_dep(name = "score_tooling", version = "1.0.2")
+bazel_dep(name = "score_tooling", version = "1.0.3")
+
+# ToDo: remove this once 1.0.4 is released,
+# since it will contain updated cr_checker
+git_override(
+ module_name = "score_tooling",
+ commit = "654664dae7df2700fd5840c5ed6c07ac6c61705d", #until 1.0.4 is released
+ remote = "https://github.com/eclipse-score/tooling.git",
+)
# ToDo: implicit dependencies for score_tooling, but needed directly here??
bazel_dep(name = "aspect_rules_lint", version = "1.10.2")
bazel_dep(name = "buildifier_prebuilt", version = "8.2.0.2")
# docs-as-code
-bazel_dep(name = "score_docs_as_code", version = "1.0.1")
+bazel_dep(name = "score_docs_as_code", version = "2.0.1")
# Provides, pytest & venv
bazel_dep(name = "score_python_basics", version = "0.3.4")
-
-bazel_dep(name = "score_platform", version = "0.3.0", dev_dependency = True)
-bazel_dep(name = "score_process", version = "1.1.0", dev_dependency = True)
+bazel_dep(name = "score_platform", version = "0.4.1")
+bazel_dep(name = "score_process", version = "1.3.1")
# Module deps
bazel_dep(name = "score_baselibs", version = "0.1.2")
@@ -132,12 +139,6 @@ git_override(
remote = "https://github.com/eclipse-score/baselibs.git",
)
-git_override(
- module_name = "score_docs_as_code",
- commit = "13ba715a95cfe85158b60d7f4748ba8e28895d8c",
- remote = "https://github.com/eclipse-score/docs-as-code.git",
-)
-
bazel_dep(name = "score_toolchains_rust", version = "0.1", dev_dependency = True)
git_override(
module_name = "score_toolchains_rust",
diff --git a/docs/index.rst b/docs/index.rst
index 4723ee67..b197a936 100644
--- a/docs/index.rst
+++ b/docs/index.rst
@@ -114,46 +114,46 @@ This crate addresses or is influenced by the following specification requirement
General Capabilities
--------------------
-- :need:`S-CORE_feat_req__persistency__cpp_rust_interop`
-- :need:`S-CORE_feat_req__persistency__multiple_kvs`
-- :need:`S-CORE_feat_req__persistency__tooling`
-- :need:`S-CORE_feat_req__persistency__variant_management`
-- :need:`S-CORE_feat_req__persistency__eng_mode`
-- :need:`S-CORE_feat_req__persistency__async_api`
-- :need:`S-CORE_feat_req__persistency__access_control`
-- :need:`S-CORE_feat_req__persistency__events`
-- :need:`S-CORE_feat_req__persistency__fast_access`
-- :need:`S-CORE_feat_req__persistency__intra_process_comm`
+- :need:`feat_req__persistency__cpp_rust_interop`
+- :need:`feat_req__persistency__multiple_kvs`
+- :need:`feat_req__persistency__tooling`
+- :need:`feat_req__persistency__variant_management`
+- :need:`feat_req__persistency__eng_mode`
+- :need:`feat_req__persistency__async_api`
+- :need:`feat_req__persistency__access_control`
+- :need:`feat_req__persistency__events`
+- :need:`feat_req__persistency__fast_access`
+- :need:`feat_req__persistency__intra_process_comm`
Data Storage and Persistency
----------------------------
-- :need:`S-CORE_feat_req__persistency__default_values`
-- :need:`S-CORE_feat_req__persistency__default_value_get`
-- :need:`S-CORE_feat_req__persistency__default_value_reset`
-- :need:`S-CORE_feat_req__persistency__persistency`
-- :need:`S-CORE_feat_req__persistency__integrity_check`
-- :need:`S-CORE_feat_req__persistency__versioning`
-- :need:`S-CORE_feat_req__persistency__update_mechanism`
-- :need:`S-CORE_feat_req__persistency__snapshots`
-- :need:`S-CORE_feat_req__persistency__persist_data`
+- :need:`feat_req__persistency__default_values`
+- :need:`feat_req__persistency__default_value_get`
+- :need:`feat_req__persistency__default_value_reset`
+- :need:`feat_req__persistency__persistency`
+- :need:`feat_req__persistency__integrity_check`
+- :need:`feat_req__persistency__versioning`
+- :need:`feat_req__persistency__update_mechanism`
+- :need:`feat_req__persistency__snapshots`
+- :need:`feat_req__persistency__persist_data`
Datatypes and Interfaces
------------------------
-- :need:`S-CORE_feat_req__persistency__support_datatype_keys`
-- :need:`S-CORE_feat_req__persistency__support_datatype_value`
+- :need:`feat_req__persistency__support_datatype_keys`
+- :need:`feat_req__persistency__support_datatype_value`
Configuration Support
---------------------
-- :need:`S-CORE_feat_req__persistency__default_value_file`
-- :need:`S-CORE_feat_req__persistency__config_file`
+- :need:`feat_req__persistency__default_value_file`
+- :need:`feat_req__persistency__config_file`
Limitations and Future Work
---------------------------
-- :need:`S-CORE_feat_req__persistency__maximum_size` *(currently unsupported)*
+- :need:`feat_req__persistency__maximum_size` *(currently unsupported)*
diff --git a/examples/README.md b/examples/README.md
new file mode 100644
index 00000000..4c117959
--- /dev/null
+++ b/examples/README.md
@@ -0,0 +1,202 @@
+
+# Getting Started with Key-Value-Storage (persistency)
+
+This guide will help you get started with the C++ and Rust implementations of the Key-Value-Storage (KVS) library, including how to use it and link it with Bazel.
+
+## 1. Integrating with Bazel
+
+### 1.1 Add this to your MODULE.bazel:
+
+ MODULE.bazel
+
+ module(name = "your_project_name")
+
+ # Add the persistency dependency (replace version as needed)
+ bazel_dep(name = "persistency", version = "0.2.0")
+
+ # Add required toolchains and dependencies for C++ and Rust
+ bazel_dep(name = "score_toolchains_gcc", version = "0.4", dev_dependency=True)
+ gcc = use_extension("@score_toolchains_gcc//extentions:gcc.bzl", "gcc", dev_dependency=True)
+ gcc.toolchain(
+ url = "https://github.com/eclipse-score/toolchains_gcc_packages/releases/download/0.0.1/x86_64-unknown-linux-gnu_gcc12.tar.gz",
+ sha256 = "457f5f20f57528033cb840d708b507050d711ae93e009388847e113b11bf3600",
+ strip_prefix = "x86_64-unknown-linux-gnu",
+ )
+ use_repo(gcc, "gcc_toolchain", "gcc_toolchain_gcc")
+
+ bazel_dep(name = "rules_rust", version = "0.61.0")
+ crate = use_extension("@rules_rust//crate_universe:extensions.bzl", "crate")
+ crate.from_specs(name = "crate_index")
+ use_repo(crate, "crate_index")
+
+ # Add any other dependencies required by persistency (see persistency's own MODULE.bazel for details)
+
+
+
+### 1.2 Insert this into your .bazelrc:
+
+ .bazelrc
+
+ ```
+ build --@score_baselibs//score/json:base_library=nlohmann
+ build --@score_baselibs//score/mw/log/flags:KRemote_Logging=False
+
+ ```
+
+
+### 1.3 Run Bazel
+If you start with a plain project add an empty file called `BUILD` into your project folder.
+
+Now you can build the project with the command:
+```sh
+bazel build //...
+```
+(So far nothing happens, because no targets were defined.)
+
+You can now continue in this guide to create a simple consumer-producer program or start on your own.
+
+
+
+## 2. Using the C++ Implementation
+
+### 2.1 Basic Usage
+
+The C++ API is centered around `KvsBuilder` and `Kvs` classes. Here is a minimal example based on the test scenarios:
+
+```cpp
+#include "kvsbuilder.hpp"
+#include
+
+int main() {
+ // Use fully qualified names instead of 'using namespace'
+ auto open_res = score::mw::per::kvs::KvsBuilder(score::mw::per::kvs::InstanceId(0))
+ .need_defaults_flag(true)
+ .need_kvs_flag(false)
+ .dir(".")
+ .build();
+ if (!open_res) {
+ std::cerr << "Failed to open KVS: " << static_cast(open_res.error()) << std::endl;
+ return 1;
+ }
+ score::mw::per::kvs::Kvs kvs = std::move(open_res.value());
+
+ // Set a key-value pair
+ kvs.set_value("username", score::mw::per::kvs::KvsValue("alice"));
+
+ // Read a value (will fall back to default if not set)
+ score::mw::per::kvs::Result get_res = kvs.get_value("username");
+ if (get_res) {
+ std::cout << "username: " << get_res.value().as_string() << std::endl;
+ }
+
+ // Read a default value (not set, but present in defaults)
+ auto get_default = kvs.get_value("language");
+ if (get_default) {
+ std::cout << "language (default): " << get_default.value().as_string() << std::endl;
+ }
+
+ // Check if a key exists (only if written)
+ if (kvs.key_exists("username").value_or(false)) {
+ std::cout << "username exists!" << std::endl;
+ }
+
+ // Remove a key
+ kvs.remove_key("username");
+
+ // List all keys (only written keys, not defaults)
+ auto keys_res = kvs.get_all_keys();
+ if (keys_res) {
+ std::cout << "All keys in KVS:" << std::endl;
+ for (const auto& key : keys_res.value()) {
+ std::cout << " " << key << std::endl;
+ }
+ }
+
+ // Flush changes to disk
+ kvs.flush();
+
+ return 0;
+}
+```
+
+## 3. Using the Rust Implementation
+
+### 3.1 Basic Usage
+
+From `examples/basic.rs`:
+```rust
+use rust_kvs::prelude::*;
+use std::collections::HashMap;
+use tempfile::tempdir;
+
+fn main() -> Result<(), ErrorCode> {
+ let dir = tempdir()?;
+ let dir_string = dir.path().to_string_lossy().to_string();
+ let instance_id = InstanceId(0);
+
+ // Build KVS instance
+ let builder = KvsBuilder::new(instance_id)
+ .dir(dir_string.clone())
+ .kvs_load(KvsLoad::Optional);
+ let kvs = builder.build()?;
+
+ // Set values
+ kvs.set_value("number", 123.0)?;
+ kvs.set_value("bool", true)?;
+ kvs.set_value("string", "First")?;
+
+ // Get value
+ let value = kvs.get_value("number")?;
+ println!("number = {:?}", value);
+
+ Ok(())
+}
+```
+
+### 3.2 Snapshots Example
+
+From `examples/snapshots.rs`:
+```rust
+let max_count = kvs.snapshot_max_count() as u32;
+for index in 0..max_count {
+ kvs.set_value("counter", index)?;
+ kvs.flush()?;
+ println!("Snapshot count: {:?}", kvs.snapshot_count());
+}
+
+// Restore a snapshot
+kvs.snapshot_restore(SnapshotId(2))?;
+```
+
+### 3.3 Defaults Example
+
+From `examples/defaults.rs`:
+```rust
+// Create defaults file and build KVS instance with defaults
+create_defaults_file(dir.path().to_path_buf(), instance_id)?;
+let builder = KvsBuilder::new(instance_id)
+ .dir(dir_string)
+ .defaults(KvsDefaults::Required);
+let kvs = builder.build()?;
+
+// Get default value
+let k1_value = kvs.get_default_value("k1")?;
+println!("k1 = {:?}", k1_value);
+```
+## 4. Default value file example
+This file should be placed in the working directory:
+```json
+{
+ "language": "en",
+ "theme": "dark",
+ "timeout": 30
+}
+```
+
+**Important:**
+- If you open the KVS with `.need_defaults_flag(true)`, the file must exist.
+- The KVS will use these defaults for any key not explicitly set.
+- You must also provide a CRC file (e.g., `defaults.json.crc`) alongside the defaults file. This CRC file is generated using the Adler-32 checksum algorithm, as implemented in the codebase. The CRC ensures the integrity of the defaults file at runtime.
+## 5. More Examples
+- See `src/cpp/tests/` for C++ test scenarios and usage patterns.
+- See `src/rust/rust_kvs/examples/` for Rust usage patterns.
diff --git a/src/cpp/src/internal/error.cpp b/src/cpp/src/internal/error.cpp
index 2b7e11c6..d78b198d 100644
--- a/src/cpp/src/internal/error.cpp
+++ b/src/cpp/src/internal/error.cpp
@@ -1,15 +1,15 @@
/********************************************************************************
-* Copyright (c) 2025 Contributors to the Eclipse Foundation
-*
-* See the NOTICE file(s) distributed with this work for additional
-* information regarding copyright ownership.
-*
-* This program and the accompanying materials are made available under the
-* terms of the Apache License Version 2.0 which is available at
-* https://www.apache.org/licenses/LICENSE-2.0
-*
-* SPDX-License-Identifier: Apache-2.0
-********************************************************************************/
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
#include "error.hpp"
namespace score::mw::per::kvs {
diff --git a/src/cpp/src/internal/error.hpp b/src/cpp/src/internal/error.hpp
index 7d6b0cb3..333c79a5 100644
--- a/src/cpp/src/internal/error.hpp
+++ b/src/cpp/src/internal/error.hpp
@@ -1,15 +1,15 @@
/********************************************************************************
-* Copyright (c) 2025 Contributors to the Eclipse Foundation
-*
-* See the NOTICE file(s) distributed with this work for additional
-* information regarding copyright ownership.
-*
-* This program and the accompanying materials are made available under the
-* terms of the Apache License Version 2.0 which is available at
-* https://www.apache.org/licenses/LICENSE-2.0
-*
-* SPDX-License-Identifier: Apache-2.0
-********************************************************************************/
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
#ifndef SCORE_LIB_KVS_INTERNAL_ERROR_HPP
#define SCORE_LIB_KVS_INTERNAL_ERROR_HPP
diff --git a/src/cpp/src/internal/kvs_helper.cpp b/src/cpp/src/internal/kvs_helper.cpp
index 81b81e7d..2b0d8d01 100644
--- a/src/cpp/src/internal/kvs_helper.cpp
+++ b/src/cpp/src/internal/kvs_helper.cpp
@@ -1,15 +1,15 @@
/********************************************************************************
-* Copyright (c) 2025 Contributors to the Eclipse Foundation
-*
-* See the NOTICE file(s) distributed with this work for additional
-* information regarding copyright ownership.
-*
-* This program and the accompanying materials are made available under the
-* terms of the Apache License Version 2.0 which is available at
-* https://www.apache.org/licenses/LICENSE-2.0
-*
-* SPDX-License-Identifier: Apache-2.0
-********************************************************************************/
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
#include "kvs_helper.hpp"
namespace score::mw::per::kvs {
diff --git a/src/cpp/src/internal/kvs_helper.hpp b/src/cpp/src/internal/kvs_helper.hpp
index 632ed6f0..7cb9c74c 100644
--- a/src/cpp/src/internal/kvs_helper.hpp
+++ b/src/cpp/src/internal/kvs_helper.hpp
@@ -1,15 +1,15 @@
/********************************************************************************
-* Copyright (c) 2025 Contributors to the Eclipse Foundation
-*
-* See the NOTICE file(s) distributed with this work for additional
-* information regarding copyright ownership.
-*
-* This program and the accompanying materials are made available under the
-* terms of the Apache License Version 2.0 which is available at
-* https://www.apache.org/licenses/LICENSE-2.0
-*
-* SPDX-License-Identifier: Apache-2.0
-********************************************************************************/
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
#ifndef SCORE_LIB_KVS_INTERNAL_KVS_HELPER_HPP
#define SCORE_LIB_KVS_INTERNAL_KVS_HELPER_HPP
diff --git a/src/cpp/src/kvs.cpp b/src/cpp/src/kvs.cpp
index 9cfd8a3d..98d99b9e 100644
--- a/src/cpp/src/kvs.cpp
+++ b/src/cpp/src/kvs.cpp
@@ -1,15 +1,15 @@
/********************************************************************************
-* Copyright (c) 2025 Contributors to the Eclipse Foundation
-*
-* See the NOTICE file(s) distributed with this work for additional
-* information regarding copyright ownership.
-*
-* This program and the accompanying materials are made available under the
-* terms of the Apache License Version 2.0 which is available at
-* https://www.apache.org/licenses/LICENSE-2.0
-*
-* SPDX-License-Identifier: Apache-2.0
-********************************************************************************/
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
#include
#include
#include
diff --git a/src/cpp/src/kvs.hpp b/src/cpp/src/kvs.hpp
index eeb6cf9b..7add8114 100644
--- a/src/cpp/src/kvs.hpp
+++ b/src/cpp/src/kvs.hpp
@@ -1,15 +1,15 @@
/********************************************************************************
-* Copyright (c) 2025 Contributors to the Eclipse Foundation
-*
-* See the NOTICE file(s) distributed with this work for additional
-* information regarding copyright ownership.
-*
-* This program and the accompanying materials are made available under the
-* terms of the Apache License Version 2.0 which is available at
-* https://www.apache.org/licenses/LICENSE-2.0
-*
-* SPDX-License-Identifier: Apache-2.0
-********************************************************************************/
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
#ifndef SCORE_LIB_KVS_KVS_HPP
#define SCORE_LIB_KVS_KVS_HPP
diff --git a/src/cpp/src/kvsbuilder.cpp b/src/cpp/src/kvsbuilder.cpp
index 267064a8..8233155d 100644
--- a/src/cpp/src/kvsbuilder.cpp
+++ b/src/cpp/src/kvsbuilder.cpp
@@ -1,15 +1,15 @@
/********************************************************************************
-* Copyright (c) 2025 Contributors to the Eclipse Foundation
-*
-* See the NOTICE file(s) distributed with this work for additional
-* information regarding copyright ownership.
-*
-* This program and the accompanying materials are made available under the
-* terms of the Apache License Version 2.0 which is available at
-* https://www.apache.org/licenses/LICENSE-2.0
-*
-* SPDX-License-Identifier: Apache-2.0
-********************************************************************************/
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
#include "kvsbuilder.hpp"
namespace score::mw::per::kvs {
diff --git a/src/cpp/src/kvsbuilder.hpp b/src/cpp/src/kvsbuilder.hpp
index 5a8095bb..6bca6e8d 100644
--- a/src/cpp/src/kvsbuilder.hpp
+++ b/src/cpp/src/kvsbuilder.hpp
@@ -1,15 +1,15 @@
/********************************************************************************
-* Copyright (c) 2025 Contributors to the Eclipse Foundation
-*
-* See the NOTICE file(s) distributed with this work for additional
-* information regarding copyright ownership.
-*
-* This program and the accompanying materials are made available under the
-* terms of the Apache License Version 2.0 which is available at
-* https://www.apache.org/licenses/LICENSE-2.0
-*
-* SPDX-License-Identifier: Apache-2.0
-********************************************************************************/
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
#ifndef SCORE_LIB_KVS_KVSBUILDER_HPP
#define SCORE_LIB_KVS_KVSBUILDER_HPP
diff --git a/src/cpp/src/kvsvalue.cpp b/src/cpp/src/kvsvalue.cpp
index 5912754b..8677eb51 100644
--- a/src/cpp/src/kvsvalue.cpp
+++ b/src/cpp/src/kvsvalue.cpp
@@ -1,15 +1,15 @@
/********************************************************************************
-* Copyright (c) 2025 Contributors to the Eclipse Foundation
-*
-* See the NOTICE file(s) distributed with this work for additional
-* information regarding copyright ownership.
-*
-* This program and the accompanying materials are made available under the
-* terms of the Apache License Version 2.0 which is available at
-* https://www.apache.org/licenses/LICENSE-2.0
-*
-* SPDX-License-Identifier: Apache-2.0
-********************************************************************************/
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
#include "kvsvalue.hpp"
namespace score::mw::per::kvs {
diff --git a/src/cpp/src/kvsvalue.hpp b/src/cpp/src/kvsvalue.hpp
index 5b336afb..e00c2373 100644
--- a/src/cpp/src/kvsvalue.hpp
+++ b/src/cpp/src/kvsvalue.hpp
@@ -1,15 +1,15 @@
/********************************************************************************
-* Copyright (c) 2025 Contributors to the Eclipse Foundation
-*
-* See the NOTICE file(s) distributed with this work for additional
-* information regarding copyright ownership.
-*
-* This program and the accompanying materials are made available under the
-* terms of the Apache License Version 2.0 which is available at
-* https://www.apache.org/licenses/LICENSE-2.0
-*
-* SPDX-License-Identifier: Apache-2.0
-********************************************************************************/
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
#ifndef SCORE_LIB_KVS_KVSVALUE_HPP
#define SCORE_LIB_KVS_KVSVALUE_HPP
diff --git a/src/cpp/tests/bm_kvs.cpp b/src/cpp/tests/bm_kvs.cpp
index 8fb85a68..a60f805b 100644
--- a/src/cpp/tests/bm_kvs.cpp
+++ b/src/cpp/tests/bm_kvs.cpp
@@ -1,15 +1,15 @@
/********************************************************************************
-* Copyright (c) 2025 Contributors to the Eclipse Foundation
-*
-* See the NOTICE file(s) distributed with this work for additional
-* information regarding copyright ownership.
-*
-* This program and the accompanying materials are made available under the
-* terms of the Apache License Version 2.0 which is available at
-* https://www.apache.org/licenses/LICENSE-2.0
-*
-* SPDX-License-Identifier: Apache-2.0
-********************************************************************************/
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
#include
#include
diff --git a/src/cpp/tests/test_kvs.cpp b/src/cpp/tests/test_kvs.cpp
index fda2db00..80730ea0 100644
--- a/src/cpp/tests/test_kvs.cpp
+++ b/src/cpp/tests/test_kvs.cpp
@@ -1,15 +1,15 @@
/********************************************************************************
-* Copyright (c) 2025 Contributors to the Eclipse Foundation
-*
-* See the NOTICE file(s) distributed with this work for additional
-* information regarding copyright ownership.
-*
-* This program and the accompanying materials are made available under the
-* terms of the Apache License Version 2.0 which is available at
-* https://www.apache.org/licenses/LICENSE-2.0
-*
-* SPDX-License-Identifier: Apache-2.0
-********************************************************************************/
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
#include "test_kvs_general.hpp"
TEST(kvs_constructor, move_constructor) {
diff --git a/src/cpp/tests/test_kvs_builder.cpp b/src/cpp/tests/test_kvs_builder.cpp
index b557df9a..d7fe3291 100644
--- a/src/cpp/tests/test_kvs_builder.cpp
+++ b/src/cpp/tests/test_kvs_builder.cpp
@@ -1,15 +1,15 @@
/********************************************************************************
-* Copyright (c) 2025 Contributors to the Eclipse Foundation
-*
-* See the NOTICE file(s) distributed with this work for additional
-* information regarding copyright ownership.
-*
-* This program and the accompanying materials are made available under the
-* terms of the Apache License Version 2.0 which is available at
-* https://www.apache.org/licenses/LICENSE-2.0
-*
-* SPDX-License-Identifier: Apache-2.0
-********************************************************************************/
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
#include "test_kvs_general.hpp"
diff --git a/src/cpp/tests/test_kvs_error.cpp b/src/cpp/tests/test_kvs_error.cpp
index e9634221..b2115b75 100644
--- a/src/cpp/tests/test_kvs_error.cpp
+++ b/src/cpp/tests/test_kvs_error.cpp
@@ -1,15 +1,15 @@
/********************************************************************************
-* Copyright (c) 2025 Contributors to the Eclipse Foundation
-*
-* See the NOTICE file(s) distributed with this work for additional
-* information regarding copyright ownership.
-*
-* This program and the accompanying materials are made available under the
-* terms of the Apache License Version 2.0 which is available at
-* https://www.apache.org/licenses/LICENSE-2.0
-*
-* SPDX-License-Identifier: Apache-2.0
-********************************************************************************/
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
#include "test_kvs_general.hpp"
diff --git a/src/cpp/tests/test_kvs_general.cpp b/src/cpp/tests/test_kvs_general.cpp
index 56bb26e4..1acbca7a 100644
--- a/src/cpp/tests/test_kvs_general.cpp
+++ b/src/cpp/tests/test_kvs_general.cpp
@@ -1,15 +1,15 @@
/********************************************************************************
-* Copyright (c) 2025 Contributors to the Eclipse Foundation
-*
-* See the NOTICE file(s) distributed with this work for additional
-* information regarding copyright ownership.
-*
-* This program and the accompanying materials are made available under the
-* terms of the Apache License Version 2.0 which is available at
-* https://www.apache.org/licenses/LICENSE-2.0
-*
-* SPDX-License-Identifier: Apache-2.0
-********************************************************************************/
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
#include "test_kvs_general.hpp"
/* adler32 control instance */
diff --git a/src/cpp/tests/test_kvs_general.hpp b/src/cpp/tests/test_kvs_general.hpp
index 9cc1ed9f..3db62bc8 100644
--- a/src/cpp/tests/test_kvs_general.hpp
+++ b/src/cpp/tests/test_kvs_general.hpp
@@ -1,15 +1,15 @@
/********************************************************************************
-* Copyright (c) 2025 Contributors to the Eclipse Foundation
-*
-* See the NOTICE file(s) distributed with this work for additional
-* information regarding copyright ownership.
-*
-* This program and the accompanying materials are made available under the
-* terms of the Apache License Version 2.0 which is available at
-* https://www.apache.org/licenses/LICENSE-2.0
-*
-* SPDX-License-Identifier: Apache-2.0
-********************************************************************************/
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
////////////////////////////////////////////////////////////////////////////////
/* The test_kvs_general files provide configuration data and methods needed for KVS tests */
diff --git a/src/cpp/tests/test_kvs_helper.cpp b/src/cpp/tests/test_kvs_helper.cpp
index 58dbfb4b..47eafd63 100644
--- a/src/cpp/tests/test_kvs_helper.cpp
+++ b/src/cpp/tests/test_kvs_helper.cpp
@@ -1,15 +1,15 @@
/********************************************************************************
-* Copyright (c) 2025 Contributors to the Eclipse Foundation
-*
-* See the NOTICE file(s) distributed with this work for additional
-* information regarding copyright ownership.
-*
-* This program and the accompanying materials are made available under the
-* terms of the Apache License Version 2.0 which is available at
-* https://www.apache.org/licenses/LICENSE-2.0
-*
-* SPDX-License-Identifier: Apache-2.0
-********************************************************************************/
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
#include "test_kvs_general.hpp"
diff --git a/src/rust/rust_kvs/examples/custom_types.rs b/src/rust/rust_kvs/examples/custom_types.rs
new file mode 100644
index 00000000..dbf47f89
--- /dev/null
+++ b/src/rust/rust_kvs/examples/custom_types.rs
@@ -0,0 +1,256 @@
+/// Example for custom types usage for KVS, with serialization and deserialization.
+/// - Implementing serialization/deserialization traits for custom types.
+/// - Handling external and nested types.
+/// - Usage with KVS.
+use rust_kvs::prelude::*;
+use std::net::IpAddr;
+use tempfile::tempdir;
+
+/// `Point` is used as an example of nested serializable objects.
+/// Type is local and traits can be provided.
+#[derive(Debug)]
+struct Point {
+ x: f64,
+ y: f64,
+}
+
+impl KvsSerialize for Point {
+ type Error = ErrorCode;
+
+ fn to_kvs(&self) -> Result {
+ let mut map = KvsMap::new();
+ map.insert("x".to_string(), self.x.to_kvs()?);
+ map.insert("y".to_string(), self.y.to_kvs()?);
+ map.to_kvs()
+ }
+}
+
+impl KvsDeserialize for Point {
+ type Error = ErrorCode;
+
+ fn from_kvs(kvs_value: &KvsValue) -> Result {
+ if let KvsValue::Object(map) = kvs_value {
+ Ok(Point {
+ x: f64::from_kvs(map.get("x").ok_or(ErrorCode::DeserializationFailed(
+ "Field not found".to_string(),
+ ))?)?,
+ y: f64::from_kvs(map.get("y").ok_or(ErrorCode::DeserializationFailed(
+ "Field not found".to_string(),
+ ))?)?,
+ })
+ } else {
+ Err(ErrorCode::DeserializationFailed(
+ "Invalid KvsValue variant provided".to_string(),
+ ))
+ }
+ }
+}
+
+/// `IpAddr` is used as an example of external type serialization.
+/// Neither `IpAddr` nor traits are local - new type pattern must be used.
+struct IpAddrWrapper(pub IpAddr);
+
+impl KvsSerialize for IpAddrWrapper {
+ type Error = ErrorCode;
+
+ fn to_kvs(&self) -> Result {
+ Ok(KvsValue::String(self.0.to_string()))
+ }
+}
+
+impl KvsDeserialize for IpAddrWrapper {
+ type Error = ErrorCode;
+
+ fn from_kvs(kvs_value: &KvsValue) -> Result {
+ if let KvsValue::String(str) = kvs_value {
+ if let Ok(ip_addr) = str.parse() {
+ Ok(IpAddrWrapper(ip_addr))
+ } else {
+ Err(ErrorCode::DeserializationFailed(
+ "KvsValue to value cast failed".to_string(),
+ ))
+ }
+ } else {
+ Err(ErrorCode::DeserializationFailed(
+ "Invalid KvsValue variant provided".to_string(),
+ ))
+ }
+ }
+}
+
+/// Main example struct.
+/// - Types defined by `KvsValue`.
+/// - `u8` - additional type not defined by `KvsValue`.
+/// - `nested` - nested serializable object.
+/// - `ip` - external type serialized to `KvsValue`.
+#[derive(Debug)]
+struct Example {
+ i32: i32,
+ u32: u32,
+ i64: i64,
+ u64: u64,
+ f64: f64,
+ bool: bool,
+ string: String,
+ vec: Vec,
+ object: KvsMap,
+ u8: u8,
+ nested: Point,
+ ip: IpAddr,
+}
+
+impl KvsSerialize for Example {
+ type Error = ErrorCode;
+
+ fn to_kvs(&self) -> Result {
+ let mut map = KvsMap::new();
+ // Types defined by `KvsValue`.
+ map.insert("i32".to_string(), self.i32.to_kvs()?);
+ map.insert("u32".to_string(), self.u32.to_kvs()?);
+ map.insert("i64".to_string(), self.i64.to_kvs()?);
+ map.insert("u64".to_string(), self.u64.to_kvs()?);
+ map.insert("f64".to_string(), self.f64.to_kvs()?);
+ map.insert("bool".to_string(), self.bool.to_kvs()?);
+ map.insert("string".to_string(), self.string.to_kvs()?);
+ map.insert("vec".to_string(), self.vec.to_kvs()?);
+ map.insert("object".to_string(), self.object.to_kvs()?);
+ map.insert("u8".to_string(), self.u8.to_kvs()?);
+
+ // Nested serializable object.
+ map.insert("nested".to_string(), self.nested.to_kvs()?);
+
+ // External type serialized to `KvsValue`.
+ map.insert("ip".to_string(), IpAddrWrapper(self.ip).to_kvs()?);
+
+ map.to_kvs()
+ }
+}
+
+impl KvsDeserialize for Example {
+ type Error = ErrorCode;
+
+ fn from_kvs(kvs_value: &KvsValue) -> Result {
+ if let KvsValue::Object(map) = kvs_value {
+ Ok(Example {
+ i32: i32::from_kvs(map.get("i32").ok_or(ErrorCode::DeserializationFailed(
+ "Field not found".to_string(),
+ ))?)?,
+ u32: u32::from_kvs(map.get("u32").ok_or(ErrorCode::DeserializationFailed(
+ "Field not found".to_string(),
+ ))?)?,
+ i64: i64::from_kvs(map.get("i64").ok_or(ErrorCode::DeserializationFailed(
+ "Field not found".to_string(),
+ ))?)?,
+ u64: u64::from_kvs(map.get("u64").ok_or(ErrorCode::DeserializationFailed(
+ "Field not found".to_string(),
+ ))?)?,
+ f64: f64::from_kvs(map.get("f64").ok_or(ErrorCode::DeserializationFailed(
+ "Field not found".to_string(),
+ ))?)?,
+ bool: bool::from_kvs(map.get("bool").ok_or(ErrorCode::DeserializationFailed(
+ "Field not found".to_string(),
+ ))?)?,
+ string: String::from_kvs(map.get("string").ok_or(
+ ErrorCode::DeserializationFailed("Field not found".to_string()),
+ )?)?,
+ vec: Vec::from_kvs(map.get("vec").ok_or(ErrorCode::DeserializationFailed(
+ "Field not found".to_string(),
+ ))?)?,
+ object: KvsMap::from_kvs(map.get("object").ok_or(
+ ErrorCode::DeserializationFailed("Field not found".to_string()),
+ )?)?,
+ u8: u8::from_kvs(map.get("u8").ok_or(ErrorCode::DeserializationFailed(
+ "Field not found".to_string(),
+ ))?)?,
+ nested: Point::from_kvs(map.get("nested").ok_or(
+ ErrorCode::DeserializationFailed("Field not found".to_string()),
+ )?)?,
+ ip: IpAddrWrapper::from_kvs(map.get("ip").ok_or(
+ ErrorCode::DeserializationFailed("Field not found".to_string()),
+ )?)?
+ .0,
+ })
+ } else {
+ Err(ErrorCode::DeserializationFailed(
+ "Invalid KvsValue variant provided".to_string(),
+ ))
+ }
+ }
+}
+
+fn main() -> Result<(), ErrorCode> {
+ // Temporary directory.
+ let dir = tempdir()?;
+ let dir_string = dir.path().to_string_lossy().to_string();
+
+ // Create initial example object.
+ let object = Example {
+ i32: -321,
+ u32: 321,
+ i64: -432,
+ u64: 432,
+ f64: 444.4,
+ bool: true,
+ string: "example".to_string(),
+ vec: vec![
+ KvsValue::from("one"),
+ KvsValue::from("two"),
+ KvsValue::from("three"),
+ ],
+ object: KvsMap::from([
+ ("first".to_string(), KvsValue::from(-123i32)),
+ ("second".to_string(), KvsValue::from(321u32)),
+ (
+ "third".to_string(),
+ KvsValue::String("map_example".to_string()),
+ ),
+ ]),
+ u8: 200,
+ nested: Point { x: 432.1, y: 654.3 },
+ ip: "127.0.0.1".parse().unwrap(),
+ };
+
+ println!("ORIGINAL OBJECT:");
+ println!("{object:#?}");
+ println!();
+
+ // Create KVS instance.
+ let kvs = KvsBuilder::new(InstanceId(0))
+ .kvs_load(KvsLoad::Ignored)
+ .defaults(KvsDefaults::Ignored)
+ .dir(dir_string)
+ .build()?;
+
+ // Serialize and set object.
+ let serialized_object = object.to_kvs().unwrap();
+ kvs.set_value("example", serialized_object.clone())?;
+
+ println!("SERIALIZED OBJECT:");
+ println!("{serialized_object:#?}");
+ println!();
+
+ // Modify and set object.
+ let modified_object = if let KvsValue::Object(mut obj) = serialized_object {
+ obj.insert("i32".to_string(), KvsValue::from(-54321i32));
+ KvsValue::Object(obj)
+ } else {
+ panic!("Invalid type");
+ };
+ kvs.set_value("example", modified_object.clone())?;
+
+ // Get object from KVS.
+ let modified_object = kvs.get_value("example")?;
+
+ println!("MODIFIED OBJECT:");
+ println!("{modified_object:#?}");
+ println!();
+
+ // Deserialize.
+ let deserialized_object = Example::from_kvs(&modified_object).unwrap();
+
+ println!("DESERIALIZED OBJECT:");
+ println!("{deserialized_object:#?}");
+ println!();
+
+ Ok(())
+}
diff --git a/src/rust/rust_kvs/src/error_code.rs b/src/rust/rust_kvs/src/error_code.rs
index bfb69c6b..ff842527 100644
--- a/src/rust/rust_kvs/src/error_code.rs
+++ b/src/rust/rust_kvs/src/error_code.rs
@@ -66,7 +66,10 @@ pub enum ErrorCode {
KeyDefaultNotFound,
/// Serialization failed
- SerializationFailed,
+ SerializationFailed(String),
+
+ /// Deserialization failed
+ DeserializationFailed(String),
/// Invalid snapshot ID
InvalidSnapshotId,
diff --git a/src/rust/rust_kvs/src/json_backend.rs b/src/rust/rust_kvs/src/json_backend.rs
index ba51b4b6..56aa542a 100644
--- a/src/rust/rust_kvs/src/json_backend.rs
+++ b/src/rust/rust_kvs/src/json_backend.rs
@@ -170,11 +170,11 @@ impl JsonBackend {
}
impl KvsBackend for JsonBackend {
- fn load_kvs(kvs_path: &Path, hash_path: Option<&PathBuf>) -> Result {
+ fn load_kvs(kvs_path: &Path, hash_path: &Path) -> Result {
if !Self::check_extension(kvs_path, "json") {
return Err(ErrorCode::KvsFileReadError);
}
- if hash_path.is_some_and(|p| !Self::check_extension(p, "hash")) {
+ if !Self::check_extension(hash_path, "hash") {
return Err(ErrorCode::KvsHashFileReadError);
}
@@ -183,27 +183,25 @@ impl KvsBackend for JsonBackend {
let json_value = Self::parse(&json_str)?;
// Perform hash check.
- if let Some(hash_path) = hash_path {
- match fs::read(hash_path) {
- Ok(hash_bytes) => {
- let hash_kvs = adler32::RollingAdler32::from_buffer(json_str.as_bytes()).hash();
- if hash_bytes.len() == 4 {
- let file_hash = u32::from_be_bytes([
- hash_bytes[0],
- hash_bytes[1],
- hash_bytes[2],
- hash_bytes[3],
- ]);
- if hash_kvs != file_hash {
- return Err(ErrorCode::ValidationFailed);
- }
- } else {
+ match fs::read(hash_path) {
+ Ok(hash_bytes) => {
+ let hash_kvs = adler32::RollingAdler32::from_buffer(json_str.as_bytes()).hash();
+ if hash_bytes.len() == 4 {
+ let file_hash = u32::from_be_bytes([
+ hash_bytes[0],
+ hash_bytes[1],
+ hash_bytes[2],
+ hash_bytes[3],
+ ]);
+ if hash_kvs != file_hash {
return Err(ErrorCode::ValidationFailed);
}
+ } else {
+ return Err(ErrorCode::ValidationFailed);
}
- Err(_) => return Err(ErrorCode::KvsHashFileReadError),
- };
- }
+ }
+ Err(e) => return Err(e.into()),
+ };
// Cast from `JsonValue` to `KvsValue`.
let kvs_value = KvsValue::from(json_value);
@@ -214,16 +212,12 @@ impl KvsBackend for JsonBackend {
}
}
- fn save_kvs(
- kvs_map: &KvsMap,
- kvs_path: &Path,
- hash_path: Option<&PathBuf>,
- ) -> Result<(), ErrorCode> {
+ fn save_kvs(kvs_map: &KvsMap, kvs_path: &Path, hash_path: &Path) -> Result<(), ErrorCode> {
// Validate extensions.
if !Self::check_extension(kvs_path, "json") {
return Err(ErrorCode::KvsFileReadError);
}
- if hash_path.is_some_and(|p| !Self::check_extension(p, "hash")) {
+ if !Self::check_extension(hash_path, "hash") {
return Err(ErrorCode::KvsHashFileReadError);
}
@@ -236,10 +230,8 @@ impl KvsBackend for JsonBackend {
fs::write(kvs_path, &json_str)?;
// Generate hash and save to hash file.
- if let Some(hash_path) = hash_path {
- let hash = adler32::RollingAdler32::from_buffer(json_str.as_bytes()).hash();
- fs::write(hash_path, hash.to_be_bytes())?
- }
+ let hash = adler32::RollingAdler32::from_buffer(json_str.as_bytes()).hash();
+ fs::write(hash_path, hash.to_be_bytes())?;
Ok(())
}
@@ -278,6 +270,14 @@ impl KvsPathResolver for JsonBackend {
fn defaults_file_path(working_dir: &Path, instance_id: InstanceId) -> PathBuf {
working_dir.join(Self::defaults_file_name(instance_id))
}
+
+ fn defaults_hash_file_name(instance_id: InstanceId) -> String {
+ format!("kvs_{instance_id}_default.hash")
+ }
+
+ fn defaults_hash_file_path(working_dir: &Path, instance_id: InstanceId) -> PathBuf {
+ working_dir.join(Self::defaults_hash_file_name(instance_id))
+ }
}
#[cfg(test)]
@@ -736,7 +736,7 @@ mod backend_tests {
]);
let kvs_path = working_dir.join("kvs.json");
let hash_path = working_dir.join("kvs.hash");
- JsonBackend::save_kvs(&kvs_map, &kvs_path, Some(&hash_path)).unwrap();
+ JsonBackend::save_kvs(&kvs_map, &kvs_path, &hash_path).unwrap();
(kvs_path, hash_path)
}
@@ -744,87 +744,86 @@ mod backend_tests {
fn test_load_kvs_ok() {
let dir = tempdir().unwrap();
let dir_path = dir.path().to_path_buf();
- let (kvs_path, _hash_path) = create_kvs_files(&dir_path);
+ let (kvs_path, hash_path) = create_kvs_files(&dir_path);
- let kvs_map = JsonBackend::load_kvs(&kvs_path, None).unwrap();
+ let kvs_map = JsonBackend::load_kvs(&kvs_path, &hash_path).unwrap();
assert_eq!(kvs_map.len(), 3);
}
#[test]
- fn test_load_kvs_not_found() {
+ fn test_load_kvs_kvs_not_found() {
let dir = tempdir().unwrap();
let dir_path = dir.path().to_path_buf();
- let kvs_path = dir_path.join("kvs.json");
+ let (kvs_path, hash_path) = create_kvs_files(&dir_path);
+ std::fs::remove_file(&kvs_path).unwrap();
- assert!(JsonBackend::load_kvs(&kvs_path, None).is_err_and(|e| e == ErrorCode::FileNotFound));
+ assert!(JsonBackend::load_kvs(&kvs_path, &hash_path)
+ .is_err_and(|e| e == ErrorCode::FileNotFound));
}
#[test]
- fn test_load_kvs_invalid_extension() {
+ fn test_load_kvs_kvs_invalid_extension() {
let dir = tempdir().unwrap();
let dir_path = dir.path().to_path_buf();
let kvs_path = dir_path.join("kvs.invalid_ext");
+ let hash_path = dir_path.join("kvs.hash");
- assert!(
- JsonBackend::load_kvs(&kvs_path, None).is_err_and(|e| e == ErrorCode::KvsFileReadError)
- );
+ assert!(JsonBackend::load_kvs(&kvs_path, &hash_path)
+ .is_err_and(|e| e == ErrorCode::KvsFileReadError));
}
#[test]
- fn test_load_kvs_malformed_json() {
+ fn test_load_kvs_hash_not_found() {
let dir = tempdir().unwrap();
let dir_path = dir.path().to_path_buf();
- let kvs_path = dir_path.join("kvs.json");
- std::fs::write(kvs_path.clone(), "{\"malformed_json\"}").unwrap();
+ let (kvs_path, hash_path) = create_kvs_files(&dir_path);
+ std::fs::remove_file(&hash_path).unwrap();
- assert!(
- JsonBackend::load_kvs(&kvs_path, None).is_err_and(|e| e == ErrorCode::JsonParserError)
- );
+ assert!(JsonBackend::load_kvs(&kvs_path, &hash_path)
+ .is_err_and(|e| e == ErrorCode::FileNotFound));
}
#[test]
- fn test_load_kvs_invalid_data() {
+ fn test_load_kvs_hash_invalid_extension() {
let dir = tempdir().unwrap();
let dir_path = dir.path().to_path_buf();
let kvs_path = dir_path.join("kvs.json");
- std::fs::write(kvs_path.clone(), "[123.4, 567.8]").unwrap();
+ let hash_path = dir_path.join("kvs.invalid_ext");
- assert!(
- JsonBackend::load_kvs(&kvs_path, None).is_err_and(|e| e == ErrorCode::JsonParserError)
- );
+ assert!(JsonBackend::load_kvs(&kvs_path, &hash_path)
+ .is_err_and(|e| e == ErrorCode::KvsHashFileReadError));
}
#[test]
- fn test_load_kvs_hash_path_some_ok() {
+ fn test_load_kvs_malformed_json() {
let dir = tempdir().unwrap();
let dir_path = dir.path().to_path_buf();
- let (kvs_path, hash_path) = create_kvs_files(&dir_path);
-
- let kvs_map = JsonBackend::load_kvs(&kvs_path, Some(&hash_path)).unwrap();
- assert_eq!(kvs_map.len(), 3);
- }
+ let kvs_path = dir_path.join("kvs.json");
+ let hash_path = dir_path.join("kvs.hash");
- #[test]
- fn test_load_kvs_hash_path_some_invalid_extension() {
- let dir = tempdir().unwrap();
- let dir_path = dir.path().to_path_buf();
- let (kvs_path, hash_path) = create_kvs_files(&dir_path);
- let new_hash_path = hash_path.with_extension("invalid_ext");
- std::fs::rename(hash_path, new_hash_path.clone()).unwrap();
+ let contents = "{\"malformed_json\"}";
+ let hash = adler32::RollingAdler32::from_buffer(contents.as_bytes()).hash();
+ std::fs::write(kvs_path.clone(), contents).unwrap();
+ std::fs::write(hash_path.clone(), hash.to_be_bytes()).unwrap();
- assert!(JsonBackend::load_kvs(&kvs_path, Some(&new_hash_path))
- .is_err_and(|e| e == ErrorCode::KvsHashFileReadError));
+ assert!(JsonBackend::load_kvs(&kvs_path, &hash_path)
+ .is_err_and(|e| e == ErrorCode::JsonParserError));
}
#[test]
- fn test_load_kvs_hash_path_some_not_found() {
+ fn test_load_kvs_invalid_data() {
let dir = tempdir().unwrap();
let dir_path = dir.path().to_path_buf();
- let (kvs_path, hash_path) = create_kvs_files(&dir_path);
- std::fs::remove_file(hash_path.clone()).unwrap();
+ let kvs_path = dir_path.join("kvs.json");
+ let hash_path = dir_path.join("kvs.hash");
- assert!(JsonBackend::load_kvs(&kvs_path, Some(&hash_path))
- .is_err_and(|e| e == ErrorCode::KvsHashFileReadError));
+ let contents = "[123.4, 567.8]";
+ let hash = adler32::RollingAdler32::from_buffer(contents.as_bytes()).hash();
+ std::fs::write(kvs_path.clone(), contents).unwrap();
+ std::fs::write(hash_path.clone(), hash.to_be_bytes()).unwrap();
+
+ assert!(JsonBackend::load_kvs(&kvs_path, &hash_path)
+ .is_err_and(|e| e == ErrorCode::JsonParserError));
}
#[test]
@@ -834,7 +833,7 @@ mod backend_tests {
let (kvs_path, hash_path) = create_kvs_files(&dir_path);
std::fs::write(hash_path.clone(), vec![0x12, 0x34, 0x56, 0x78]).unwrap();
- assert!(JsonBackend::load_kvs(&kvs_path, Some(&hash_path))
+ assert!(JsonBackend::load_kvs(&kvs_path, &hash_path)
.is_err_and(|e| e == ErrorCode::ValidationFailed));
}
@@ -845,7 +844,7 @@ mod backend_tests {
let (kvs_path, hash_path) = create_kvs_files(&dir_path);
std::fs::write(hash_path.clone(), vec![0x12, 0x34, 0x56]).unwrap();
- assert!(JsonBackend::load_kvs(&kvs_path, Some(&hash_path))
+ assert!(JsonBackend::load_kvs(&kvs_path, &hash_path)
.is_err_and(|e| e == ErrorCode::ValidationFailed));
}
@@ -860,49 +859,35 @@ mod backend_tests {
("k3".to_string(), KvsValue::from(123.4)),
]);
let kvs_path = dir_path.join("kvs.json");
- JsonBackend::save_kvs(&kvs_map, &kvs_path, None).unwrap();
+ let hash_path = dir_path.join("kvs.hash");
+ JsonBackend::save_kvs(&kvs_map, &kvs_path, &hash_path).unwrap();
assert!(kvs_path.exists());
}
#[test]
- fn test_save_kvs_invalid_extension() {
+ fn test_save_kvs_kvs_invalid_extension() {
let dir = tempdir().unwrap();
let dir_path = dir.path().to_path_buf();
let kvs_map = KvsMap::new();
let kvs_path = dir_path.join("kvs.invalid_ext");
- assert!(JsonBackend::save_kvs(&kvs_map, &kvs_path, None)
- .is_err_and(|e| e == ErrorCode::KvsFileReadError));
- }
-
- #[test]
- fn test_save_kvs_hash_path_some_ok() {
- let dir = tempdir().unwrap();
- let dir_path = dir.path().to_path_buf();
-
- let kvs_map = KvsMap::from([
- ("k1".to_string(), KvsValue::from("v1")),
- ("k2".to_string(), KvsValue::from(true)),
- ("k3".to_string(), KvsValue::from(123.4)),
- ]);
- let kvs_path = dir_path.join("kvs.json");
let hash_path = dir_path.join("kvs.hash");
- JsonBackend::save_kvs(&kvs_map, &kvs_path, Some(&hash_path)).unwrap();
- assert!(kvs_path.exists());
- assert!(hash_path.exists());
+ assert!(JsonBackend::save_kvs(&kvs_map, &kvs_path, &hash_path)
+ .is_err_and(|e| e == ErrorCode::KvsFileReadError));
}
#[test]
- fn test_save_kvs_hash_path_some_invalid_extension() {
+ fn test_save_kvs_hash_invalid_extension() {
let dir = tempdir().unwrap();
let dir_path = dir.path().to_path_buf();
let kvs_map = KvsMap::new();
let kvs_path = dir_path.join("kvs.json");
let hash_path = dir_path.join("kvs.invalid_ext");
- assert!(JsonBackend::save_kvs(&kvs_map, &kvs_path, Some(&hash_path))
+
+ assert!(JsonBackend::save_kvs(&kvs_map, &kvs_path, &hash_path)
.is_err_and(|e| e == ErrorCode::KvsHashFileReadError));
}
@@ -913,7 +898,9 @@ mod backend_tests {
let kvs_map = KvsMap::from([("inf".to_string(), KvsValue::from(f64::INFINITY))]);
let kvs_path = dir_path.join("kvs.json");
- assert!(JsonBackend::save_kvs(&kvs_map, &kvs_path, None)
+ let hash_path = dir_path.join("kvs.hash");
+
+ assert!(JsonBackend::save_kvs(&kvs_map, &kvs_path, &hash_path)
.is_err_and(|e| e == ErrorCode::JsonGeneratorError));
}
}
@@ -984,4 +971,23 @@ mod path_resolver_tests {
let act_name = JsonBackend::defaults_file_path(dir_path, instance_id);
assert_eq!(exp_name, act_name);
}
+
+ #[test]
+ fn test_defaults_hash_file_name() {
+ let instance_id = InstanceId(123);
+ let exp_name = format!("kvs_{instance_id}_default.hash");
+ let act_name = JsonBackend::defaults_hash_file_name(instance_id);
+ assert_eq!(exp_name, act_name);
+ }
+
+ #[test]
+ fn test_defaults_hash_file_path() {
+ let dir = tempdir().unwrap();
+ let dir_path = dir.path();
+
+ let instance_id = InstanceId(123);
+ let exp_name = dir_path.join(format!("kvs_{instance_id}_default.hash"));
+ let act_name = JsonBackend::defaults_hash_file_path(dir_path, instance_id);
+ assert_eq!(exp_name, act_name);
+ }
}
diff --git a/src/rust/rust_kvs/src/kvs.rs b/src/rust/rust_kvs/src/kvs.rs
index d193f601..d36676ad 100644
--- a/src/rust/rust_kvs/src/kvs.rs
+++ b/src/rust/rust_kvs/src/kvs.rs
@@ -393,7 +393,7 @@ impl KvsApi
);
let data = self.data.lock()?;
- Backend::save_kvs(&data.kvs_map, &kvs_path, Some(&hash_path)).map_err(|e| {
+ Backend::save_kvs(&data.kvs_map, &kvs_path, &hash_path).map_err(|e| {
eprintln!("error: save_kvs failed: {e:?}");
e
})?;
@@ -473,7 +473,7 @@ impl KvsApi
self.parameters.instance_id,
snapshot_id,
);
- data.kvs_map = Backend::load_kvs(&kvs_path, Some(&hash_path))?;
+ data.kvs_map = Backend::load_kvs(&kvs_path, &hash_path)?;
Ok(())
}
@@ -530,7 +530,7 @@ mod kvs_tests {
use crate::kvs_backend::{KvsBackend, KvsPathResolver};
use crate::kvs_builder::KvsData;
use crate::kvs_value::{KvsMap, KvsValue};
- use std::path::PathBuf;
+ use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex};
use tempfile::tempdir;
@@ -539,17 +539,14 @@ mod kvs_tests {
struct MockBackend;
impl KvsBackend for MockBackend {
- fn load_kvs(
- _kvs_path: &std::path::Path,
- _hash_path: Option<&PathBuf>,
- ) -> Result {
+ fn load_kvs(_kvs_path: &Path, _hash_path: &Path) -> Result {
unimplemented!()
}
fn save_kvs(
_kvs_map: &KvsMap,
- _kvs_path: &std::path::Path,
- _hash_path: Option<&PathBuf>,
+ _kvs_path: &Path,
+ _hash_path: &Path,
) -> Result<(), ErrorCode> {
unimplemented!()
}
@@ -561,7 +558,7 @@ mod kvs_tests {
}
fn kvs_file_path(
- _working_dir: &std::path::Path,
+ _working_dir: &Path,
_instance_id: InstanceId,
_snapshot_id: SnapshotId,
) -> PathBuf {
@@ -573,7 +570,7 @@ mod kvs_tests {
}
fn hash_file_path(
- _working_dir: &std::path::Path,
+ _working_dir: &Path,
_instance_id: InstanceId,
_snapshot_id: SnapshotId,
) -> PathBuf {
@@ -584,7 +581,15 @@ mod kvs_tests {
unimplemented!()
}
- fn defaults_file_path(_working_dir: &std::path::Path, _instance_id: InstanceId) -> PathBuf {
+ fn defaults_file_path(_working_dir: &Path, _instance_id: InstanceId) -> PathBuf {
+ unimplemented!()
+ }
+
+ fn defaults_hash_file_name(_instance_id: InstanceId) -> String {
+ unimplemented!()
+ }
+
+ fn defaults_hash_file_path(_working_dir: &Path, _instance_id: InstanceId) -> PathBuf {
unimplemented!()
}
}
diff --git a/src/rust/rust_kvs/src/kvs_backend.rs b/src/rust/rust_kvs/src/kvs_backend.rs
index 2b22edf4..622faa66 100644
--- a/src/rust/rust_kvs/src/kvs_backend.rs
+++ b/src/rust/rust_kvs/src/kvs_backend.rs
@@ -17,14 +17,10 @@ use std::path::{Path, PathBuf};
/// KVS backend interface.
pub trait KvsBackend {
/// Load KvsMap from given file.
- fn load_kvs(kvs_path: &Path, hash_path: Option<&PathBuf>) -> Result;
+ fn load_kvs(kvs_path: &Path, hash_path: &Path) -> Result;
/// Store KvsMap at given file path.
- fn save_kvs(
- kvs_map: &KvsMap,
- kvs_path: &Path,
- hash_path: Option<&PathBuf>,
- ) -> Result<(), ErrorCode>;
+ fn save_kvs(kvs_map: &KvsMap, kvs_path: &Path, hash_path: &Path) -> Result<(), ErrorCode>;
}
/// KVS path resolver interface.
@@ -54,4 +50,10 @@ pub trait KvsPathResolver {
/// Get defaults file path in working directory.
fn defaults_file_path(working_dir: &Path, instance_id: InstanceId) -> PathBuf;
+
+ /// Get defaults hash file name.
+ fn defaults_hash_file_name(instance_id: InstanceId) -> String;
+
+ /// Get defaults hash file path in working directory.
+ fn defaults_hash_file_path(working_dir: &Path, instance_id: InstanceId) -> PathBuf;
}
diff --git a/src/rust/rust_kvs/src/kvs_builder.rs b/src/rust/rust_kvs/src/kvs_builder.rs
index 957631fc..31076a38 100644
--- a/src/rust/rust_kvs/src/kvs_builder.rs
+++ b/src/rust/rust_kvs/src/kvs_builder.rs
@@ -259,16 +259,18 @@ impl GenericKvsBuilder KvsMap::new(),
KvsDefaults::Optional => {
if defaults_path.exists() {
- Backend::load_kvs(&defaults_path, None)?
+ Backend::load_kvs(&defaults_path, &defaults_hash_path)?
} else {
KvsMap::new()
}
}
- KvsDefaults::Required => Backend::load_kvs(&defaults_path, None)?,
+ KvsDefaults::Required => Backend::load_kvs(&defaults_path, &defaults_hash_path)?,
};
// Load KVS and hash files.
@@ -281,12 +283,12 @@ impl GenericKvsBuilder KvsMap::new(),
KvsLoad::Optional => {
if kvs_path.exists() && hash_path.exists() {
- Backend::load_kvs(&kvs_path, Some(&hash_path))?
+ Backend::load_kvs(&kvs_path, &hash_path)?
} else {
KvsMap::new()
}
}
- KvsLoad::Required => Backend::load_kvs(&kvs_path, Some(&hash_path))?,
+ KvsLoad::Required => Backend::load_kvs(&kvs_path, &hash_path)?,
};
// Shared object containing data.
@@ -602,16 +604,18 @@ mod kvs_builder_tests {
fn create_defaults_file(
working_dir: &Path,
instance_id: InstanceId,
- ) -> Result {
+ ) -> Result<(PathBuf, PathBuf), ErrorCode> {
let defaults_file_path = TestBackend::defaults_file_path(working_dir, instance_id);
+ let defaults_hash_file_path =
+ TestBackend::defaults_hash_file_path(working_dir, instance_id);
let kvs_map = KvsMap::from([
("number1".to_string(), KvsValue::F64(123.0)),
("bool1".to_string(), KvsValue::Boolean(true)),
("string1".to_string(), KvsValue::String("Hello".to_string())),
]);
- TestBackend::save_kvs(&kvs_map, &defaults_file_path, None)?;
+ TestBackend::save_kvs(&kvs_map, &defaults_file_path, &defaults_hash_file_path)?;
- Ok(defaults_file_path)
+ Ok((defaults_file_path, defaults_hash_file_path))
}
/// Generate and store files containing example KVS and hash data.
@@ -627,7 +631,7 @@ mod kvs_builder_tests {
("bool1".to_string(), KvsValue::Boolean(false)),
("string1".to_string(), KvsValue::String("Hi".to_string())),
]);
- TestBackend::save_kvs(&kvs_map, &kvs_file_path, Some(&hash_file_path))?;
+ TestBackend::save_kvs(&kvs_map, &kvs_file_path, &hash_file_path)?;
Ok((kvs_file_path, hash_file_path))
}
diff --git a/src/rust/rust_kvs/src/kvs_serialize.rs b/src/rust/rust_kvs/src/kvs_serialize.rs
new file mode 100644
index 00000000..668b576a
--- /dev/null
+++ b/src/rust/rust_kvs/src/kvs_serialize.rs
@@ -0,0 +1,613 @@
+// Copyright (c) 2025 Contributors to the Eclipse Foundation
+//
+// See the NOTICE file(s) distributed with this work for additional
+// information regarding copyright ownership.
+//
+// This program and the accompanying materials are made available under the
+// terms of the Apache License Version 2.0 which is available at
+//
+//
+// SPDX-License-Identifier: Apache-2.0
+
+use crate::error_code::ErrorCode;
+use crate::kvs_value::{KvsMap, KvsValue};
+
+/// `KvsValue` serialization trait.
+/// Allows object to be serialized into `KvsValue`.
+pub trait KvsSerialize {
+ type Error;
+
+ /// Serialize object to `KvsValue`.
+ fn to_kvs(&self) -> Result;
+}
+
+macro_rules! impl_kvs_serialize_for_t_unchecked_cast {
+ ($t:ty, $internal_t:ty, $variant:ident) => {
+ impl KvsSerialize for $t {
+ type Error = ErrorCode;
+
+ fn to_kvs(&self) -> Result {
+ Ok(KvsValue::$variant(self.clone() as $internal_t))
+ }
+ }
+ };
+}
+
+macro_rules! impl_kvs_serialize_for_t_checked_cast {
+ ($t:ty, $internal_t:ty, $variant:ident) => {
+ impl KvsSerialize for $t {
+ type Error = ErrorCode;
+
+ fn to_kvs(&self) -> Result {
+ if let Ok(casted) = <$internal_t>::try_from(self.clone()) {
+ Ok(KvsValue::$variant(casted))
+ } else {
+ Err(ErrorCode::SerializationFailed(
+ "Value to KvsValue cast failed".to_string(),
+ ))
+ }
+ }
+ }
+ };
+}
+
+macro_rules! impl_kvs_serialize_for_t {
+ ($t:ty, $variant:ident) => {
+ impl KvsSerialize for $t {
+ type Error = ErrorCode;
+
+ fn to_kvs(&self) -> Result {
+ Ok(KvsValue::$variant(self.clone()))
+ }
+ }
+ };
+}
+
+impl_kvs_serialize_for_t_unchecked_cast!(i8, i32, I32);
+impl_kvs_serialize_for_t_unchecked_cast!(i16, i32, I32);
+impl_kvs_serialize_for_t!(i32, I32);
+impl_kvs_serialize_for_t!(i64, I64);
+impl_kvs_serialize_for_t_checked_cast!(isize, i64, I64);
+impl_kvs_serialize_for_t_unchecked_cast!(u8, u32, U32);
+impl_kvs_serialize_for_t_unchecked_cast!(u16, u32, U32);
+impl_kvs_serialize_for_t!(u32, U32);
+impl_kvs_serialize_for_t!(u64, U64);
+impl_kvs_serialize_for_t_checked_cast!(usize, u64, U64);
+impl_kvs_serialize_for_t_unchecked_cast!(f32, f64, F64);
+impl_kvs_serialize_for_t!(f64, F64);
+impl_kvs_serialize_for_t!(bool, Boolean);
+impl_kvs_serialize_for_t!(String, String);
+impl_kvs_serialize_for_t!(Vec, Array);
+impl_kvs_serialize_for_t!(KvsMap, Object);
+
+impl KvsSerialize for &str {
+ type Error = ErrorCode;
+
+ fn to_kvs(&self) -> Result {
+ Ok(KvsValue::String(self.to_string()))
+ }
+}
+
+impl KvsSerialize for () {
+ type Error = ErrorCode;
+
+ fn to_kvs(&self) -> Result {
+ Ok(KvsValue::Null)
+ }
+}
+
+/// `KvsValue` deserialization trait.
+/// Allows object to be deserialized from `KvsValue`.
+pub trait KvsDeserialize: Sized {
+ type Error;
+
+ /// Deserialize object from `KvsValue`.
+ fn from_kvs(kvs_value: &KvsValue) -> Result;
+}
+
+macro_rules! impl_kvs_deserialize_for_t_checked_cast {
+ ($t:ty, $variant:ident) => {
+ impl KvsDeserialize for $t {
+ type Error = ErrorCode;
+
+ fn from_kvs(kvs_value: &KvsValue) -> Result {
+ if let KvsValue::$variant(value) = kvs_value {
+ if let Ok(casted) = <$t>::try_from(value.clone()) {
+ Ok(casted)
+ } else {
+ Err(ErrorCode::DeserializationFailed(
+ "KvsValue to value cast failed".to_string(),
+ ))
+ }
+ } else {
+ Err(ErrorCode::DeserializationFailed(
+ "Invalid KvsValue variant provided".to_string(),
+ ))
+ }
+ }
+ }
+ };
+}
+
+macro_rules! impl_kvs_deserialize_for_t {
+ ($t:ty, $variant:ident) => {
+ impl KvsDeserialize for $t {
+ type Error = ErrorCode;
+
+ fn from_kvs(kvs_value: &KvsValue) -> Result {
+ if let KvsValue::$variant(value) = kvs_value {
+ Ok(value.clone())
+ } else {
+ Err(ErrorCode::DeserializationFailed(
+ "Invalid KvsValue variant provided".to_string(),
+ ))
+ }
+ }
+ }
+ };
+}
+
+impl_kvs_deserialize_for_t_checked_cast!(i8, I32);
+impl_kvs_deserialize_for_t_checked_cast!(i16, I32);
+impl_kvs_deserialize_for_t!(i32, I32);
+impl_kvs_deserialize_for_t!(i64, I64);
+impl_kvs_deserialize_for_t_checked_cast!(isize, I64);
+impl_kvs_deserialize_for_t_checked_cast!(u8, U32);
+impl_kvs_deserialize_for_t_checked_cast!(u16, U32);
+impl_kvs_deserialize_for_t!(u32, U32);
+impl_kvs_deserialize_for_t!(u64, U64);
+impl_kvs_deserialize_for_t_checked_cast!(usize, U64);
+impl_kvs_deserialize_for_t!(f64, F64);
+impl_kvs_deserialize_for_t!(bool, Boolean);
+impl_kvs_deserialize_for_t!(String, String);
+impl_kvs_deserialize_for_t!(Vec, Array);
+impl_kvs_deserialize_for_t!(KvsMap, Object);
+
+/// Edge case - `TryFrom` is not implemented for `f32`.
+/// Unchecked `as` conversion must be used.
+impl KvsDeserialize for f32 {
+ type Error = ErrorCode;
+
+ fn from_kvs(kvs_value: &KvsValue) -> Result {
+ if let KvsValue::F64(value) = kvs_value {
+ Ok(*value as f32)
+ } else {
+ Err(ErrorCode::DeserializationFailed(
+ "Invalid KvsValue variant provided".to_string(),
+ ))
+ }
+ }
+}
+
+impl KvsDeserialize for () {
+ type Error = ErrorCode;
+
+ fn from_kvs(kvs_value: &KvsValue) -> Result {
+ if let KvsValue::Null = kvs_value {
+ Ok(())
+ } else {
+ Err(ErrorCode::DeserializationFailed(
+ "Invalid KvsValue variant provided".to_string(),
+ ))
+ }
+ }
+}
+
+#[cfg(test)]
+mod serialize_tests {
+ use crate::kvs_serialize::KvsSerialize;
+ use crate::kvs_value::{KvsMap, KvsValue};
+
+ #[test]
+ fn test_i8_ok() {
+ let value = i8::MIN;
+ let kvs_value = value.to_kvs().unwrap();
+ assert_eq!(kvs_value, KvsValue::I32(value as i32));
+ }
+
+ #[test]
+ fn test_i16_ok() {
+ let value = i16::MIN;
+ let kvs_value = value.to_kvs().unwrap();
+ assert_eq!(kvs_value, KvsValue::I32(value as i32));
+ }
+
+ #[test]
+ fn test_i32_ok() {
+ let value = i32::MIN;
+ let kvs_value = value.to_kvs().unwrap();
+ assert_eq!(kvs_value, KvsValue::I32(value));
+ }
+
+ #[test]
+ fn test_i64_ok() {
+ let value = i64::MIN;
+ let kvs_value = value.to_kvs().unwrap();
+ assert_eq!(kvs_value, KvsValue::I64(value));
+ }
+
+ #[test]
+ fn test_isize_ok() {
+ let value = isize::MIN;
+ let kvs_value = value.to_kvs().unwrap();
+ assert_eq!(kvs_value, KvsValue::I64(value as i64));
+ }
+
+ #[test]
+ fn test_u8_ok() {
+ let value = u8::MIN;
+ let kvs_value = value.to_kvs().unwrap();
+ assert_eq!(kvs_value, KvsValue::U32(value as u32));
+ }
+
+ #[test]
+ fn test_u16_ok() {
+ let value = u16::MIN;
+ let kvs_value = value.to_kvs().unwrap();
+ assert_eq!(kvs_value, KvsValue::U32(value as u32));
+ }
+
+ #[test]
+ fn test_u32_ok() {
+ let value = u32::MIN;
+ let kvs_value = value.to_kvs().unwrap();
+ assert_eq!(kvs_value, KvsValue::U32(value));
+ }
+
+ #[test]
+ fn test_u64_ok() {
+ let value = u64::MIN;
+ let kvs_value = value.to_kvs().unwrap();
+ assert_eq!(kvs_value, KvsValue::U64(value));
+ }
+
+ #[test]
+ fn test_usize_ok() {
+ let value = usize::MIN;
+ let kvs_value = value.to_kvs().unwrap();
+ assert_eq!(kvs_value, KvsValue::U64(value as u64));
+ }
+
+ #[test]
+ fn test_f32_ok() {
+ let value = f32::MIN;
+ let kvs_value = value.to_kvs().unwrap();
+ assert_eq!(kvs_value, KvsValue::F64(value as f64));
+ }
+
+ #[test]
+ fn test_f64_ok() {
+ let value = f64::MIN;
+ let kvs_value = value.to_kvs().unwrap();
+ assert_eq!(kvs_value, KvsValue::F64(value));
+ }
+
+ #[test]
+ fn test_bool_ok() {
+ let value = true;
+ let kvs_value = value.to_kvs().unwrap();
+ assert_eq!(kvs_value, KvsValue::Boolean(value));
+ }
+
+ #[test]
+ fn test_string_ok() {
+ let value = "test".to_string();
+ let kvs_value = value.to_kvs().unwrap();
+ assert_eq!(kvs_value, KvsValue::String(value));
+ }
+
+ #[test]
+ fn test_str_ok() {
+ let value = "test";
+ let kvs_value = value.to_kvs().unwrap();
+ assert_eq!(kvs_value, KvsValue::String(value.to_string()));
+ }
+
+ #[test]
+ fn test_array_ok() {
+ let value = vec![
+ KvsValue::String("one".to_string()),
+ KvsValue::String("two".to_string()),
+ KvsValue::String("three".to_string()),
+ ];
+ let kvs_value = value.to_kvs().unwrap();
+ assert_eq!(kvs_value, KvsValue::Array(value));
+ }
+
+ #[test]
+ fn test_object_ok() {
+ let value = KvsMap::from([
+ ("first".to_string(), KvsValue::from(-321i32)),
+ ("second".to_string(), KvsValue::from(1234u32)),
+ ("third".to_string(), KvsValue::from(true)),
+ ]);
+ let kvs_value = value.to_kvs().unwrap();
+ assert_eq!(kvs_value, KvsValue::Object(value));
+ }
+
+ #[test]
+ fn test_unit_ok() {
+ let value = ();
+ let kvs_value = value.to_kvs().unwrap();
+ assert_eq!(kvs_value, KvsValue::Null);
+ }
+}
+
+#[cfg(test)]
+mod deserialize_tests {
+ use crate::error_code::ErrorCode;
+ use crate::kvs_serialize::KvsDeserialize;
+ use crate::kvs_value::{KvsMap, KvsValue};
+
+ // NOTE: Only internally up-casted types require out of range tests.
+ // For other types it's not possible to represent such scenario.
+
+ #[test]
+ fn test_i8_ok() {
+ let kvs_value = KvsValue::I32(i8::MIN as i32);
+ let value = i8::from_kvs(&kvs_value).unwrap();
+ assert_eq!(value, *kvs_value.get::().unwrap() as i8);
+ }
+
+ #[test]
+ fn test_i8_invalid_variant() {
+ let kvs_value = KvsValue::String("invalid string".to_string());
+ let result = i8::from_kvs(&kvs_value);
+ assert!(result.is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("Invalid KvsValue variant provided".to_string())));
+ }
+
+ #[test]
+ fn test_i8_out_of_range() {
+ let kvs_value = KvsValue::I32(i32::MAX);
+ let result = i8::from_kvs(&kvs_value);
+ assert!(result
+ .is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("KvsValue to value cast failed".to_string())));
+ }
+
+ #[test]
+ fn test_i16_ok() {
+ let kvs_value = KvsValue::I32(i16::MIN as i32);
+ let value = i16::from_kvs(&kvs_value).unwrap();
+ assert_eq!(value, *kvs_value.get::().unwrap() as i16);
+ }
+
+ #[test]
+ fn test_i16_invalid_variant() {
+ let kvs_value = KvsValue::String("invalid string".to_string());
+ let result = i16::from_kvs(&kvs_value);
+ assert!(result.is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("Invalid KvsValue variant provided".to_string())));
+ }
+
+ #[test]
+ fn test_i16_out_of_range() {
+ let kvs_value = KvsValue::I32(i32::MAX);
+ let result = i16::from_kvs(&kvs_value);
+ assert!(result
+ .is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("KvsValue to value cast failed".to_string())));
+ }
+
+ #[test]
+ fn test_i32_ok() {
+ let kvs_value = KvsValue::I32(i32::MIN);
+ let value = i32::from_kvs(&kvs_value).unwrap();
+ assert_eq!(value, *kvs_value.get::().unwrap());
+ }
+
+ #[test]
+ fn test_i32_invalid_variant() {
+ let kvs_value = KvsValue::String("invalid string".to_string());
+ let result = i32::from_kvs(&kvs_value);
+ assert!(result.is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("Invalid KvsValue variant provided".to_string())));
+ }
+
+ #[test]
+ fn test_i64_ok() {
+ let kvs_value = KvsValue::I64(i64::MIN);
+ let value = i64::from_kvs(&kvs_value).unwrap();
+ assert_eq!(value, *kvs_value.get::().unwrap());
+ }
+
+ #[test]
+ fn test_i64_invalid_variant() {
+ let kvs_value = KvsValue::String("invalid string".to_string());
+ let result = i64::from_kvs(&kvs_value);
+ assert!(result.is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("Invalid KvsValue variant provided".to_string())));
+ }
+
+ #[test]
+ fn test_isize_ok() {
+ let kvs_value = KvsValue::I64(isize::MIN as i64);
+ let value = isize::from_kvs(&kvs_value).unwrap();
+ assert_eq!(value, *kvs_value.get::().unwrap() as isize);
+ }
+
+ #[test]
+ fn test_isize_invalid_variant() {
+ let kvs_value = KvsValue::String("invalid string".to_string());
+ let result = isize::from_kvs(&kvs_value);
+ assert!(result.is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("Invalid KvsValue variant provided".to_string())));
+ }
+
+ #[test]
+ fn test_u8_ok() {
+ let kvs_value = KvsValue::U32(u8::MIN as u32);
+ let value = u8::from_kvs(&kvs_value).unwrap();
+ assert_eq!(value, *kvs_value.get::().unwrap() as u8);
+ }
+
+ #[test]
+ fn test_u8_invalid_variant() {
+ let kvs_value = KvsValue::String("invalid string".to_string());
+ let result = u8::from_kvs(&kvs_value);
+ assert!(result.is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("Invalid KvsValue variant provided".to_string())));
+ }
+
+ #[test]
+ fn test_u8_out_of_range() {
+ let kvs_value = KvsValue::U32(u32::MAX);
+ let result = u8::from_kvs(&kvs_value);
+ assert!(result
+ .is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("KvsValue to value cast failed".to_string())));
+ }
+
+ #[test]
+ fn test_u16_ok() {
+ let kvs_value = KvsValue::U32(u16::MIN as u32);
+ let value = u16::from_kvs(&kvs_value).unwrap();
+ assert_eq!(value, *kvs_value.get::().unwrap() as u16);
+ }
+
+ #[test]
+ fn test_u16_invalid_variant() {
+ let kvs_value = KvsValue::String("invalid string".to_string());
+ let result = u16::from_kvs(&kvs_value);
+ assert!(result.is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("Invalid KvsValue variant provided".to_string())));
+ }
+
+ #[test]
+ fn test_u16_out_of_range() {
+ let kvs_value = KvsValue::U32(u32::MAX);
+ let result = u16::from_kvs(&kvs_value);
+ assert!(result
+ .is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("KvsValue to value cast failed".to_string())));
+ }
+
+ #[test]
+ fn test_u32_ok() {
+ let kvs_value = KvsValue::U32(u32::MIN);
+ let value = u32::from_kvs(&kvs_value).unwrap();
+ assert_eq!(value, *kvs_value.get::().unwrap());
+ }
+
+ #[test]
+ fn test_u32_invalid_variant() {
+ let kvs_value = KvsValue::String("invalid string".to_string());
+ let result = u32::from_kvs(&kvs_value);
+ assert!(result.is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("Invalid KvsValue variant provided".to_string())));
+ }
+
+ #[test]
+ fn test_u64_ok() {
+ let kvs_value = KvsValue::U64(u64::MIN);
+ let value = u64::from_kvs(&kvs_value).unwrap();
+ assert_eq!(value, *kvs_value.get::().unwrap());
+ }
+
+ #[test]
+ fn test_u64_invalid_variant() {
+ let kvs_value = KvsValue::String("invalid string".to_string());
+ let result = u64::from_kvs(&kvs_value);
+ assert!(result.is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("Invalid KvsValue variant provided".to_string())));
+ }
+
+ #[test]
+ fn test_usize_ok() {
+ let kvs_value = KvsValue::U64(usize::MIN as u64);
+ let value = usize::from_kvs(&kvs_value).unwrap();
+ assert_eq!(value, *kvs_value.get::().unwrap() as usize);
+ }
+
+ #[test]
+ fn test_usize_invalid_variant() {
+ let kvs_value = KvsValue::String("invalid string".to_string());
+ let result = usize::from_kvs(&kvs_value);
+ assert!(result.is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("Invalid KvsValue variant provided".to_string())));
+ }
+
+ #[test]
+ fn test_bool_ok() {
+ let kvs_value = KvsValue::Boolean(true);
+ let value = bool::from_kvs(&kvs_value).unwrap();
+ assert_eq!(value, *kvs_value.get::().unwrap());
+ }
+
+ #[test]
+ fn test_bool_invalid_variant() {
+ let kvs_value = KvsValue::String("invalid string".to_string());
+ let result = bool::from_kvs(&kvs_value);
+ assert!(result.is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("Invalid KvsValue variant provided".to_string())));
+ }
+
+ #[test]
+ fn test_string_ok() {
+ let kvs_value = KvsValue::String("test".to_string());
+ let value = String::from_kvs(&kvs_value).unwrap();
+ assert_eq!(value, *kvs_value.get::().unwrap());
+ }
+
+ #[test]
+ fn test_string_invalid_variant() {
+ let kvs_value = KvsValue::Boolean(true);
+ let result = String::from_kvs(&kvs_value);
+ assert!(result.is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("Invalid KvsValue variant provided".to_string())));
+ }
+
+ #[test]
+ fn test_array_ok() {
+ let kvs_value = KvsValue::Array(vec![
+ KvsValue::String("one".to_string()),
+ KvsValue::String("two".to_string()),
+ KvsValue::String("three".to_string()),
+ ]);
+ let value = Vec::from_kvs(&kvs_value).unwrap();
+ assert_eq!(value, *kvs_value.get::>().unwrap());
+ }
+
+ #[test]
+ fn test_array_invalid_variant() {
+ let kvs_value = KvsValue::String("invalid string".to_string());
+ let result = Vec::from_kvs(&kvs_value);
+ assert!(result.is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("Invalid KvsValue variant provided".to_string())));
+ }
+
+ #[test]
+ fn test_object_ok() {
+ let kvs_value = KvsValue::Object(KvsMap::from([
+ ("first".to_string(), KvsValue::from(-321i32)),
+ ("second".to_string(), KvsValue::from(1234u32)),
+ ("third".to_string(), KvsValue::from(true)),
+ ]));
+ let value = KvsMap::from_kvs(&kvs_value).unwrap();
+ assert_eq!(value, *kvs_value.get::().unwrap());
+ }
+
+ #[test]
+ fn test_object_invalid_variant() {
+ let kvs_value = KvsValue::String("invalid string".to_string());
+ let result = KvsMap::from_kvs(&kvs_value);
+ assert!(result.is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("Invalid KvsValue variant provided".to_string())));
+ }
+
+ #[test]
+ fn test_unit_ok() {
+ let kvs_value = KvsValue::Null;
+ <()>::from_kvs(&kvs_value).unwrap();
+ // No need for comparing unit values.
+ }
+
+ #[test]
+ fn test_unit_invalid_variant() {
+ let kvs_value = KvsValue::String("invalid string".to_string());
+ let result = <()>::from_kvs(&kvs_value);
+ assert!(result.is_err_and(|e| e
+ == ErrorCode::DeserializationFailed("Invalid KvsValue variant provided".to_string())));
+ }
+}
diff --git a/src/rust/rust_kvs/src/lib.rs b/src/rust/rust_kvs/src/lib.rs
index c274b591..544bea28 100644
--- a/src/rust/rust_kvs/src/lib.rs
+++ b/src/rust/rust_kvs/src/lib.rs
@@ -138,6 +138,7 @@ pub mod kvs_api;
mod kvs_backend;
pub mod kvs_builder;
pub mod kvs_mock;
+pub mod kvs_serialize;
pub mod kvs_value;
use json_backend::JsonBackend;
@@ -150,6 +151,7 @@ pub mod prelude {
pub use crate::kvs::GenericKvs;
pub use crate::kvs_api::{InstanceId, KvsApi, KvsDefaults, KvsLoad, SnapshotId};
pub use crate::kvs_builder::GenericKvsBuilder;
+ pub use crate::kvs_serialize::{KvsDeserialize, KvsSerialize};
pub use crate::kvs_value::{KvsMap, KvsValue};
pub use crate::{Kvs, KvsBuilder};
}
diff --git a/tests/cpp_test_scenarios/BUILD b/tests/cpp_test_scenarios/BUILD
index fcf988fe..a8814431 100644
--- a/tests/cpp_test_scenarios/BUILD
+++ b/tests/cpp_test_scenarios/BUILD
@@ -1,16 +1,28 @@
+# *******************************************************************************
+# Copyright (c) 2025 Contributors to the Eclipse Foundation
+#
+# See the NOTICE file(s) distributed with this work for additional
+# information regarding copyright ownership.
+#
+# This program and the accompanying materials are made available under the
+# terms of the Apache License Version 2.0 which is available at
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# SPDX-License-Identifier: Apache-2.0
+# *******************************************************************************
load("@rules_cc//cc:cc_binary.bzl", "cc_binary")
cc_binary(
name = "cpp_test_scenarios",
- srcs = [
- "src/main.cpp",
- "src/test_basic.cpp",
- "src/test_basic.hpp",
- ],
+ srcs = glob([
+ "src/**/*.cpp",
+ "src/**/*.hpp",
+ ]),
copts = [
"-g",
],
- visibility = ["//tests/python_test_cases:__pkg__"],
+ includes = ["src"],
+ visibility = ["//visibility:public"],
deps = [
"//src/cpp/src:kvs_cpp",
"@score_test_scenarios//test_scenarios_cpp",
diff --git a/tests/cpp_test_scenarios/src/cit/test_default_values.cpp b/tests/cpp_test_scenarios/src/cit/test_default_values.cpp
new file mode 100644
index 00000000..65af5d5b
--- /dev/null
+++ b/tests/cpp_test_scenarios/src/cit/test_default_values.cpp
@@ -0,0 +1,465 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+
+#include "test_default_values.hpp"
+#include "helpers/kvs_instance.hpp"
+#include "helpers/kvs_parameters.hpp"
+#include "tracing.hpp"
+#include
+
+using score::mw::per::kvs::KvsValue;
+
+namespace test_default_values {
+
+constexpr double kFloatEpsilon = 1e-6;
+const std::string kTargetName{"cpp_test_scenarios::cit::default_values"};
+
+inline bool float_equal(double a, double b, double epsilon = kFloatEpsilon) {
+ return std::abs(a - b) < epsilon;
+}
+
+/**
+ * Helper to log key/value state in a format parsable by Python tests.
+ *
+ * @param key The key being queried or modified in the KVS.
+ * @param value_is_default String encoding whether the current value matches the
+ * default ("Ok(true)", "Ok(false)", or error string).
+ * @param default_value String encoding the default value for the key (e.g.,
+ * "Ok(F64(...))" or error string).
+ * @param current_value String encoding the current value for the key (e.g.,
+ * "Ok(F64(...))" or error string).
+ *
+ * This function emits logs in a structured format so that the Python test suite
+ * can parse and validate scenario output.
+ */
+static void info_log(const std::string& key,
+ const std::string& value_is_default,
+ const std::string& default_value,
+ const std::string& current_value) {
+ TRACING_INFO(kTargetName, std::pair{std::string{"key"}, key},
+ std::pair{std::string{"value_is_default"}, value_is_default},
+ std::pair{std::string{"default_value"}, default_value},
+ std::pair{std::string{"current_value"}, current_value});
+}
+
+/**
+ * Helper to log key/value state in a format parsable by Python tests.
+ *
+ * @param key The key being queried or modified in the KVS.
+ * @param value_is_default String encoding whether the current value matches the
+ * default ("Ok(true)", "Ok(false)", or error string).
+ * @param default_value String encoding the default value for the key (e.g.,
+ * "Ok(F64(...))" or error string).
+ * @param current_value String encoding the current value for the key (e.g.,
+ * "Ok(F64(...))" or error string).
+ *
+ * This function emits logs in a structured format so that the Python test suite
+ * can parse and validate scenario output.
+ */
+template
+static void info_log(const std::string& key, const bool value_is_default,
+ T current_value) {
+ TRACING_INFO(kTargetName, std::pair{std::string{"key"}, key},
+ std::pair{std::string{"value_is_default"}, value_is_default},
+ std::pair{std::string{"current_value"}, current_value});
+}
+
+std::string DefaultValuesScenario::name() const { return "default_values"; }
+void DefaultValuesScenario::run(const std::string& input) const {
+
+ using namespace score::mw::per::kvs;
+ std::string key = "test_number";
+ auto params = map_to_params(input);
+ auto kvs = kvs_instance(params);
+
+ {
+ // First check: log initial state before any set_value
+ auto get_default_result = kvs.get_default_value(key);
+ auto get_value_result = kvs.get_value(key);
+ std::string value_is_default;
+ std::string default_value;
+ std::string current_value;
+
+ // Default value
+ if (!get_default_result.has_value() ||
+ get_default_result.value().getType() != KvsValue::Type::f64 ||
+ !std::holds_alternative(
+ get_default_result.value().getValue())) {
+ default_value = "Err(KeyNotFound)";
+ } else {
+ std::ostringstream oss;
+ oss.precision(1);
+ oss << std::fixed
+ << std::get(get_default_result.value().getValue());
+ default_value = "Ok(F64(" + oss.str() + "))";
+ }
+
+ // Current value
+ if (!get_value_result.has_value() ||
+ get_value_result.value().getType() != KvsValue::Type::f64 ||
+ !std::holds_alternative(
+ get_value_result.value().getValue())) {
+ current_value = "Err(KeyNotFound)";
+ } else {
+ std::ostringstream oss;
+ oss.precision(1);
+ oss << std::fixed
+ << std::get(get_value_result.value().getValue());
+ current_value = "Ok(F64(" + oss.str() + "))";
+ }
+
+ // value_is_default
+ if (default_value == "Err(KeyNotFound)" ||
+ current_value == "Err(KeyNotFound)") {
+ value_is_default = "Err(KeyNotFound)";
+ } else if (float_equal(
+ std::get(get_default_result.value().getValue()),
+ std::get(get_value_result.value().getValue()))) {
+ value_is_default = "Ok(true)";
+ } else {
+ value_is_default = "Ok(false)";
+ }
+
+ info_log(key, value_is_default, default_value, current_value);
+
+ auto set_result = kvs.set_value(key, KvsValue{432.1});
+ if (!set_result)
+ throw std::runtime_error("Failed to set value");
+ kvs.flush();
+ }
+
+ {
+ // Second check: log after set_value and flush
+ // - value_is_default: Ok(true) if value == default, Ok(false) if not,
+ // Err(KeyNotFound) if default missing
+ auto kvs = kvs_instance(params);
+
+ auto get_default_result = kvs.get_default_value(key);
+ auto get_value_result = kvs.get_value(key);
+ std::string value_is_default = "Ok(false)";
+ std::string default_value;
+ std::string current_value;
+ bool get_default_ok = get_default_result.has_value();
+ bool get_value_ok = get_value_result.has_value();
+ const KvsValue* def_val =
+ get_default_ok ? &get_default_result.value() : nullptr;
+ const KvsValue* cur_val =
+ get_value_ok ? &get_value_result.value() : nullptr;
+ // Defensive: check types before accessing variant
+ if (!cur_val || !def_val) {
+ // If either value is missing, skip variant access
+ } else {
+ bool both_f64 = cur_val->getType() == def_val->getType() &&
+ cur_val->getType() == KvsValue::Type::f64;
+ if (both_f64) {
+ try {
+ if (float_equal(std::get(cur_val->getValue()),
+ std::get(def_val->getValue())))
+ value_is_default = "Ok(true)";
+ } catch (const std::bad_variant_access& e) {
+ throw;
+ }
+ }
+ }
+
+ // Format default_value for log
+ if (get_default_ok && def_val->getType() == KvsValue::Type::f64) {
+ try {
+ std::ostringstream oss;
+ oss.precision(1);
+ oss << std::fixed << std::get(def_val->getValue());
+ default_value = "Ok(F64(" + oss.str() + "))";
+ } catch (const std::bad_variant_access& e) {
+ throw;
+ }
+ } else if (get_default_ok) {
+ default_value =
+ "Err(UnexpectedType:" +
+ std::to_string(static_cast(def_val->getType())) + ")";
+ } else {
+ default_value = "Err(KeyNotFound)";
+ }
+
+ // Format current_value for log
+ if (get_value_ok && cur_val->getType() == KvsValue::Type::f64) {
+ try {
+ std::ostringstream oss;
+ oss.precision(1);
+ oss << std::fixed << std::get(cur_val->getValue());
+ current_value = "Ok(F64(" + oss.str() + "))";
+ } catch (const std::bad_variant_access& e) {
+ throw;
+ }
+ } else if (get_value_ok) {
+ current_value =
+ "Err(UnexpectedType:" +
+ std::to_string(static_cast(cur_val->getType())) + ")";
+ } else {
+ current_value = "Err(KeyNotFound)";
+ }
+
+ info_log(key, value_is_default, default_value,
+ current_value); // Log after set/flush
+ }
+}
+
+std::string RemoveKeyScenario::name() const { return "remove_key"; }
+void RemoveKeyScenario::run(const std::string& input) const {
+ using namespace score::mw::per::kvs;
+ std::string key = "test_number";
+ auto params = map_to_params(input);
+ auto kvs = kvs_instance(params);
+
+ auto get_default = kvs.get_default_value(key);
+ auto get_value = kvs.get_value(key);
+ std::string value_is_default;
+ std::string default_value;
+ std::string current_value;
+
+ // Default value
+ if (!get_default || get_default->getType() != KvsValue::Type::f64 ||
+ !std::holds_alternative(get_default->getValue())) {
+ default_value = "Err(KeyNotFound)";
+ } else {
+ std::ostringstream oss;
+ oss.precision(1);
+ oss << std::fixed << std::get(get_default->getValue());
+ default_value = "Ok(F64(" + oss.str() + "))";
+ }
+
+ // Current value
+ if (!get_value || get_value->getType() != KvsValue::Type::f64 ||
+ !std::holds_alternative(get_value->getValue())) {
+ current_value = "Err(KeyNotFound)";
+ } else {
+ std::ostringstream oss;
+ oss.precision(1);
+ oss << std::fixed << std::get(get_value->getValue());
+ current_value = "Ok(F64(" + oss.str() + "))";
+ }
+
+ // value_is_default
+ if (default_value == "Err(KeyNotFound)" ||
+ current_value == "Err(KeyNotFound)") {
+ value_is_default = "Err(KeyNotFound)";
+ } else if (float_equal(std::get(get_default->getValue()),
+ std::get(get_value->getValue()))) {
+ value_is_default = "Ok(true)";
+ } else {
+ value_is_default = "Ok(false)";
+ }
+
+ info_log(key, value_is_default, default_value, current_value);
+
+ auto set_result = kvs.set_value(key, KvsValue{432.1});
+ if (!set_result)
+ throw std::runtime_error("Failed to set value");
+ get_value = kvs.get_value(key);
+
+ // Second check: log after set_value
+ // - value_is_default: Ok(true) if value == default, Ok(false) if not
+ value_is_default = "Ok(false)";
+ if (get_value && get_default &&
+ get_value->getType() == get_default->getType() &&
+ get_value->getType() == KvsValue::Type::f64) {
+ if (float_equal(std::get(get_value->getValue()),
+ std::get(get_default->getValue())))
+ value_is_default = "Ok(true)";
+ }
+
+ // Format current_value for log
+ if (get_value && get_value->getType() == KvsValue::Type::f64) {
+ std::ostringstream oss;
+ oss.precision(1);
+ oss << std::fixed << std::get(get_value->getValue());
+ current_value = "Ok(F64(" + oss.str() + "))";
+ } else {
+ current_value = "Err(KeyNotFound)";
+ }
+
+ info_log(key, value_is_default, default_value,
+ current_value); // Log after set
+
+ auto remove_result = kvs.remove_key(key);
+ if (!remove_result)
+ throw std::runtime_error("Failed to remove key");
+ get_value = kvs.get_value(key);
+
+ // Third check: log after remove_key
+ // - value_is_default: Err(KeyNotFound) if default missing, Ok(true) if
+ // value
+ // == default, Ok(false) otherwise
+ if (!get_default) {
+ value_is_default =
+ "Err(KeyNotFound)"; // Defensive: default missing after remove
+ } else {
+ value_is_default = "Ok(false)";
+ if (get_value && get_default &&
+ get_value->getType() == get_default->getType() &&
+ get_value->getType() == KvsValue::Type::f64) {
+ if (float_equal(std::get(get_value->getValue()),
+ std::get(get_default->getValue())))
+ value_is_default = "Ok(true)";
+ }
+ }
+
+ // Format current_value for log
+ if (get_value && get_value->getType() == KvsValue::Type::f64) {
+ std::ostringstream oss;
+ oss.precision(1);
+ oss << std::fixed << std::get(get_value->getValue());
+ current_value = "Ok(F64(" + oss.str() + "))";
+ } else {
+ current_value = "Err(KeyNotFound)";
+ }
+
+ info_log(key, value_is_default, default_value,
+ current_value); // Log after remove
+}
+
+std::string ResetAllKeysScenario::name() const { return "reset_all_keys"; }
+void ResetAllKeysScenario::run(const std::string& input) const {
+ using namespace score::mw::per::kvs;
+ const int num_values = 5;
+ auto params = map_to_params(input);
+ auto kvs = kvs_instance(params);
+
+ std::vector> key_values;
+ for (int i = 0; i < num_values; ++i) {
+ key_values.emplace_back("test_number_" + std::to_string(i), 123.4 * i);
+ }
+
+ for (const auto& [key, value] : key_values) {
+ {
+ const bool value_is_default = kvs.has_default_value(key).value();
+ const double current_value =
+ std::get((*kvs.get_value(key)).getValue());
+
+ info_log(key, value_is_default, current_value);
+ }
+
+ kvs.set_value(key, KvsValue{value});
+
+ {
+ const bool value_is_default = kvs.has_default_value(key).value();
+ const double current_value =
+ std::get((*kvs.get_value(key)).getValue());
+
+ info_log(key, value_is_default, current_value);
+ }
+ }
+
+ kvs.reset();
+ for (const auto& [key, _] : key_values) {
+ const bool value_is_default = kvs.has_default_value(key).value();
+ const double current_value =
+ std::get((*kvs.get_value(key)).getValue());
+
+ info_log(key, value_is_default, current_value);
+ }
+}
+
+std::string ResetSingleKeyScenario::name() const { return "reset_single_key"; }
+void ResetSingleKeyScenario::run(const std::string& input) const {
+ using namespace score::mw::per::kvs;
+ int num_values = 5;
+ int reset_index = 2;
+ auto params = map_to_params(input);
+ auto kvs = kvs_instance(params);
+
+ std::vector> key_values;
+ for (int i = 0; i < num_values; ++i) {
+ key_values.emplace_back("test_number_" + std::to_string(i), 123.4 * i);
+ }
+
+ for (const auto& [key, value] : key_values) {
+ {
+ const bool value_is_default = kvs.has_default_value(key).value();
+ const double current_value =
+ std::get((*kvs.get_value(key)).getValue());
+
+ info_log(key, value_is_default, current_value);
+ }
+ // Set value.
+ kvs.set_value(key, KvsValue{value});
+
+ // Get value parameters after set.
+ {
+ const bool value_is_default = kvs.has_default_value(key).value();
+ const double current_value =
+ std::get((*kvs.get_value(key)).getValue());
+
+ info_log(key, value_is_default, current_value);
+ }
+ }
+
+ // Reset single key.
+ auto reset_result = kvs.reset_key(key_values[reset_index].first);
+ if (!reset_result)
+ throw std::runtime_error("Failed to reset key");
+
+ // Use KVS APIs to get value_is_default and current_value after reset
+ for (const auto& [key, value] : key_values) {
+ const bool value_is_default = kvs.has_default_value(key).value();
+ const double current_value =
+ std::get((*kvs.get_value(key)).getValue());
+ info_log(key, value_is_default, current_value);
+ }
+}
+
+std::string ChecksumScenario::name() const { return "checksum"; }
+void ChecksumScenario::run(const std::string& input) const {
+ using namespace score::mw::per::kvs;
+ auto params = map_to_params(input);
+ std::string kvs_path, hash_path;
+ try {
+ auto kvs = kvs_instance(params);
+ kvs.flush();
+
+ // Get kvs_path
+ auto kvs_path_res = kvs.get_kvs_filename(0);
+ if (kvs_path_res.has_value()) {
+ kvs_path = static_cast(kvs_path_res.value());
+ } else {
+ kvs_path = "";
+ }
+
+ // Get hash_path
+ auto hash_path_res = kvs.get_hash_filename(0);
+ if (hash_path_res.has_value()) {
+ hash_path = static_cast(hash_path_res.value());
+ } else {
+ hash_path = "";
+ }
+ } catch (const std::exception& e) {
+ kvs_path = "";
+ hash_path = "";
+ }
+
+ // Log using Rust-compatible field names for Python test parsing
+ TRACING_INFO(kTargetName, std::pair{std::string{"kvs_path"}, kvs_path},
+ std::pair{std::string{"hash_path"}, hash_path});
+}
+
+// Default values group
+ScenarioGroup::Ptr create_default_values_group() {
+ return ScenarioGroup::Ptr{
+ new ScenarioGroupImpl{"default_values",
+ {std::make_shared(),
+ std::make_shared(),
+ std::make_shared(),
+ std::make_shared(),
+ std::make_shared()},
+ {}}};
+}
+} // namespace test_default_values
diff --git a/tests/cpp_test_scenarios/src/cit/test_default_values.hpp b/tests/cpp_test_scenarios/src/cit/test_default_values.hpp
new file mode 100644
index 00000000..e7d18e37
--- /dev/null
+++ b/tests/cpp_test_scenarios/src/cit/test_default_values.hpp
@@ -0,0 +1,56 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#pragma once
+
+#include "scenario.hpp"
+
+namespace test_default_values {
+
+class DefaultValuesScenario final : public Scenario {
+ public:
+ ~DefaultValuesScenario() final = default;
+ std::string name() const final;
+ void run(const std::string& input) const final;
+};
+
+class RemoveKeyScenario final : public Scenario {
+ public:
+ ~RemoveKeyScenario() final = default;
+ std::string name() const final;
+ void run(const std::string& input) const final;
+};
+
+class ResetAllKeysScenario final : public Scenario {
+ public:
+ ~ResetAllKeysScenario() final = default;
+ std::string name() const final;
+ void run(const std::string& input) const final;
+};
+
+class ResetSingleKeyScenario final : public Scenario {
+ public:
+ ~ResetSingleKeyScenario() final = default;
+ std::string name() const final;
+ void run(const std::string& input) const final;
+};
+
+class ChecksumScenario final : public Scenario {
+ public:
+ ~ChecksumScenario() final = default;
+ std::string name() const final;
+ void run(const std::string& input) const final;
+};
+
+// Default values group
+ScenarioGroup::Ptr create_default_values_group();
+} // namespace test_default_values
diff --git a/tests/cpp_test_scenarios/src/helpers/kvs_instance.hpp b/tests/cpp_test_scenarios/src/helpers/kvs_instance.hpp
new file mode 100644
index 00000000..44ed3254
--- /dev/null
+++ b/tests/cpp_test_scenarios/src/helpers/kvs_instance.hpp
@@ -0,0 +1,42 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+
+#pragma once
+#include "kvs_parameters.hpp"
+#include
+
+static score::mw::per::kvs::Kvs kvs_instance(const KvsParameters& params) {
+ using namespace score::mw::per::kvs;
+ InstanceId instance_id{params.instance_id};
+ KvsBuilder builder{instance_id};
+
+ if (params.need_defaults.has_value()) {
+ builder = builder.need_defaults_flag(*params.need_defaults);
+ }
+ if (params.need_kvs.has_value()) {
+ builder = builder.need_kvs_flag(*params.need_kvs);
+ }
+ if (params.dir.has_value()) {
+ builder = builder.dir(std::string(*params.dir));
+ }
+
+ auto kvs_ptr = builder.build();
+ if (!kvs_ptr) {
+ throw ScenarioError(
+ score::mw::per::kvs::ErrorCode::JsonParserError,
+ "KVS creation failed: build() returned null (possible "
+ "file not found, JSON parse error, or corruption)");
+ }
+
+ return std::move(*kvs_ptr);
+}
diff --git a/tests/cpp_test_scenarios/src/helpers/kvs_parameters.hpp b/tests/cpp_test_scenarios/src/helpers/kvs_parameters.hpp
new file mode 100644
index 00000000..ed066a20
--- /dev/null
+++ b/tests/cpp_test_scenarios/src/helpers/kvs_parameters.hpp
@@ -0,0 +1,94 @@
+/********************************************************************************
+ * Copyright (c) 2025 Contributors to the Eclipse Foundation
+ *
+ * See the NOTICE file(s) distributed with this work for additional
+ * information regarding copyright ownership.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Apache License Version 2.0 which is available at
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * SPDX-License-Identifier: Apache-2.0
+ ********************************************************************************/
+#pragma once
+
+#include
+
+// Custom exception type for error code propagation
+class ScenarioError : public std::runtime_error {
+ public:
+ score::mw::per::kvs::ErrorCode code;
+ ScenarioError(score::mw::per::kvs::ErrorCode code, const std::string& msg)
+ : std::runtime_error(msg), code(code) {}
+};
+
+namespace {
+
+struct KvsParameters {
+ uint64_t instance_id;
+ std::optional need_defaults;
+ std::optional need_kvs;
+ std::optional dir;
+};
+
+KvsParameters map_to_params(const std::string& data) {
+ using namespace score::json;
+
+ JsonParser parser;
+ auto any_res{parser.FromBuffer(data)};
+ if (!any_res) {
+ throw ScenarioError(score::mw::per::kvs::ErrorCode::JsonParserError,
+ "Failed to parse JSON data");
+ }
+ const auto& map_root{
+ any_res.value().As