From 3a4438faa2349b58fc43d0c72216a93cdcab6603 Mon Sep 17 00:00:00 2001 From: William Lyles <26171886+wilyle@users.noreply.github.com> Date: Mon, 2 Oct 2023 13:20:20 -0700 Subject: [PATCH] Improve configuration for digital twin adapters and mock (#56) Improve configuration for digital twin adapters and mock Closes #17 --- Cargo.toml | 1 + common/src/config_utils.rs | 10 ++- .../Cargo.toml | 7 +- .../README.md | 19 +++-- .../build.rs | 19 +++-- .../res/config.json | 32 -------- ...in_memory_digital_twin_config.default.json | 34 ++++++++ .../src/config.rs | 7 ++ .../in_memory_mock_digital_twin_adapter.rs | 77 ++++++++----------- .../mock_digital_twin_adapter/Cargo.toml | 5 +- .../mock_digital_twin_adapter/README.md | 6 +- .../mock_digital_twin_adapter/build.rs | 20 ++--- .../mock_digital_twin_adapter/res/config.json | 3 - ...k_digital_twin_adapter_config.default.json | 3 + .../mock_digital_twin_adapter/src/config.rs | 10 +-- .../src/mock_digital_twin_adapter.rs | 42 +++++----- .../in_memory_mock_mapping_client/Cargo.toml | 1 - mocks/mock_digital_twin/Cargo.toml | 7 +- mocks/mock_digital_twin/README.md | 31 ++++---- mocks/mock_digital_twin/build.rs | 20 ++--- mocks/mock_digital_twin/res/config.json | 60 --------------- .../res/mock_digital_twin_config.default.json | 54 +++++++++++++ mocks/mock_digital_twin/src/config.rs | 23 ++---- mocks/mock_digital_twin/src/main.rs | 69 ++++++++--------- 24 files changed, 277 insertions(+), 283 deletions(-) delete mode 100644 digital_twin_adapters/in_memory_mock_digital_twin_adapter/res/config.json create mode 100644 digital_twin_adapters/in_memory_mock_digital_twin_adapter/res/in_memory_digital_twin_config.default.json delete mode 100644 digital_twin_adapters/mock_digital_twin_adapter/res/config.json create mode 100644 digital_twin_adapters/mock_digital_twin_adapter/res/mock_digital_twin_adapter_config.default.json delete mode 100644 mocks/mock_digital_twin/res/config.json create mode 100644 mocks/mock_digital_twin/res/mock_digital_twin_config.default.json diff --git a/Cargo.toml b/Cargo.toml index 6fd834fb..36a77876 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ members = [ "freyja", "mapping_clients/in_memory_mock_mapping_client", "mapping_clients/mock_mapping_service_client", + "mocks/mock_digital_twin", "mocks/mock_mapping_service", "proc_macros", "provider_proxies/grpc/v1", diff --git a/common/src/config_utils.rs b/common/src/config_utils.rs index 978cd088..6369d817 100644 --- a/common/src/config_utils.rs +++ b/common/src/config_utils.rs @@ -58,10 +58,12 @@ where Err(_) => { // The path below resolves to $HOME/.freyja/config/{overrides_filename} home_dir() - .ok_or(io_error_handler(std::io::Error::new( - std::io::ErrorKind::Other, - "Could not retrieve home directory", - )))? + .ok_or_else(|| { + io_error_handler(std::io::Error::new( + std::io::ErrorKind::Other, + "Could not retrieve home directory", + )) + })? .join(DOT_FREYJA_DIR) .join(CONFIG_DIR) .join(overrides_filename) diff --git a/digital_twin_adapters/in_memory_mock_digital_twin_adapter/Cargo.toml b/digital_twin_adapters/in_memory_mock_digital_twin_adapter/Cargo.toml index 12e11f15..d3f964c2 100644 --- a/digital_twin_adapters/in_memory_mock_digital_twin_adapter/Cargo.toml +++ b/digital_twin_adapters/in_memory_mock_digital_twin_adapter/Cargo.toml @@ -10,7 +10,10 @@ license = "MIT" [dependencies] async-trait = { workspace = true } +freyja-common = { workspace = true } freyja-contracts = { workspace = true } serde = { workspace = true } -serde_json = { workspace = true } -tokio = { workspace = true } \ No newline at end of file +tokio = { workspace = true } + +[build-dependencies] +freyja-build-common = { workspace = true } \ No newline at end of file diff --git a/digital_twin_adapters/in_memory_mock_digital_twin_adapter/README.md b/digital_twin_adapters/in_memory_mock_digital_twin_adapter/README.md index 6f009eef..d9e73a88 100644 --- a/digital_twin_adapters/in_memory_mock_digital_twin_adapter/README.md +++ b/digital_twin_adapters/in_memory_mock_digital_twin_adapter/README.md @@ -4,11 +4,14 @@ The In-Memory Mock Digital Twin Adapter mocks the behavior of an in-vehicle digi ## Configuration -The adapter's config is located at `res/config.json` and will be copied to the build output automatically. This file is a list of `EntityConfig` objects with the following properties: - -- `entity`: a digital twin entity that will be exposed to the `find_by_id` API. Entities contain the following properties: - - `id`: this is used as the key when calling `find_by_id`. - - `uri`: the uri that is used to invoke a provider. This is a stand-in for whatever the provider contact info is from Ibeji. This is used as the key when calling `subscribe` and `get` in the [In-Memory Provider Proxy](../../provider_proxies/in_memory_mock_provider_proxy/). - - `operation`: the operation that should be used to access this entity. Supported values are `Get` and `Subscribe`. - - `protocol`: the communication protocol that should be used to access this entity. For this particular adapter, the value should always be `in-memory`. - - `name` and `description`: these are currently unused by Freyja. They are included for completeness and parity with Ibeji's Digital Twin Service contract and may potentially be logged. +This adapter supports the following configuration settings: + +- `values`: a list of entities to use. Each entry in the list is an object with the following properties: + - `entity`: a digital twin entity that will be exposed to the `find_by_id` API. Entities contain the following properties: + - `id`: this is used as the key when calling `find_by_id`. + - `uri`: the uri that is used to invoke a provider. This is a stand-in for whatever the provider contact info is from Ibeji. This is used as the key when calling `subscribe` and `get` in the [In-Memory Provider Proxy](../../provider_proxies/in_memory_mock_provider_proxy/). + - `operation`: the operation that should be used to access this entity. + - `protocol`: the communication protocol that should be used to access this entity. For this particular adapter, the value should always be `in-memory`. + - `name` and `description`: these are currently unused by Freyja. They are included for completeness and parity with Ibeji's Digital Twin Service contract and may potentially be logged. + +This adapter supports [config overrides](../../docs/config-overrides.md). The override filename is `in_memory_digital_twin_config.json`, and the default config is located at `res/in_memory_digital_twin_config.default.json`. diff --git a/digital_twin_adapters/in_memory_mock_digital_twin_adapter/build.rs b/digital_twin_adapters/in_memory_mock_digital_twin_adapter/build.rs index e1d1c175..4d8bcb53 100644 --- a/digital_twin_adapters/in_memory_mock_digital_twin_adapter/build.rs +++ b/digital_twin_adapters/in_memory_mock_digital_twin_adapter/build.rs @@ -2,16 +2,19 @@ // Licensed under the MIT license. // SPDX-License-Identifier: MIT -use std::{env, fs, path::Path}; +use std::env; -fn main() { - // Current directory of the build script is the package's root directory - let config_path = env::current_dir().unwrap().join("res").join("config.json"); +use freyja_build_common::copy_to_build_out_dir; - let target_dir = env::var("OUT_DIR").unwrap(); - let dest_path = Path::new(&target_dir).join("config.json"); +const RES_DIR_NAME: &str = "res"; +const DEFAULT_CONFIG_FILE: &str = "in_memory_digital_twin_config.default.json"; - fs::copy(&config_path, dest_path).unwrap(); +fn main() { + // Current directory of the build script is the package's root directory + let config_path = env::current_dir() + .unwrap() + .join(RES_DIR_NAME) + .join(DEFAULT_CONFIG_FILE); - println!("cargo:rerun-if-changed={}", config_path.to_str().unwrap()); + copy_to_build_out_dir(config_path, DEFAULT_CONFIG_FILE); } diff --git a/digital_twin_adapters/in_memory_mock_digital_twin_adapter/res/config.json b/digital_twin_adapters/in_memory_mock_digital_twin_adapter/res/config.json deleted file mode 100644 index 628b7966..00000000 --- a/digital_twin_adapters/in_memory_mock_digital_twin_adapter/res/config.json +++ /dev/null @@ -1,32 +0,0 @@ -[ - { - "entity": { - "id": "dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1", - "name": "AmbientAirTemperature", - "uri": "http://0.0.0.0:1111", - "description": "The immediate surroundings air temperature (in Fahrenheit).", - "operation": "Get", - "protocol": "in-memory" - } - }, - { - "entity": { - "id": "dtmi:sdv:Vehicle:Cabin:HVAC:IsAirConditioningActive;1", - "name": "IsAirConditioningActive", - "uri": "http://0.0.0.0:1111", - "description": "Is air conditioning active?", - "operation": "Subscribe", - "protocol": "in-memory" - } - }, - { - "entity": { - "id": "dtmi:sdv:Vehicle:OBD:HybridBatteryRemaining;1", - "name": "HybridBatteryRemaining", - "uri": "http://0.0.0.0:1111", - "description": "Percentage of the hybrid battery remaining", - "operation": "Subscribe", - "protocol": "in-memory" - } - } -] \ No newline at end of file diff --git a/digital_twin_adapters/in_memory_mock_digital_twin_adapter/res/in_memory_digital_twin_config.default.json b/digital_twin_adapters/in_memory_mock_digital_twin_adapter/res/in_memory_digital_twin_config.default.json new file mode 100644 index 00000000..bd297922 --- /dev/null +++ b/digital_twin_adapters/in_memory_mock_digital_twin_adapter/res/in_memory_digital_twin_config.default.json @@ -0,0 +1,34 @@ +{ + "values": [ + { + "entity": { + "id": "dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1", + "name": "AmbientAirTemperature", + "uri": "http://0.0.0.0:1111", + "description": "The immediate surroundings air temperature (in Fahrenheit).", + "operation": "Get", + "protocol": "in-memory" + } + }, + { + "entity": { + "id": "dtmi:sdv:Vehicle:Cabin:HVAC:IsAirConditioningActive;1", + "name": "IsAirConditioningActive", + "uri": "http://0.0.0.0:1111", + "description": "Is air conditioning active?", + "operation": "Subscribe", + "protocol": "in-memory" + } + }, + { + "entity": { + "id": "dtmi:sdv:Vehicle:OBD:HybridBatteryRemaining;1", + "name": "HybridBatteryRemaining", + "uri": "http://0.0.0.0:1111", + "description": "Percentage of the hybrid battery remaining", + "operation": "Subscribe", + "protocol": "in-memory" + } + } + ] +} \ No newline at end of file diff --git a/digital_twin_adapters/in_memory_mock_digital_twin_adapter/src/config.rs b/digital_twin_adapters/in_memory_mock_digital_twin_adapter/src/config.rs index aaf7e230..d86cce36 100644 --- a/digital_twin_adapters/in_memory_mock_digital_twin_adapter/src/config.rs +++ b/digital_twin_adapters/in_memory_mock_digital_twin_adapter/src/config.rs @@ -6,6 +6,13 @@ use serde::{Deserialize, Serialize}; use freyja_contracts::entity::Entity; +/// The in-memory mock digital twin's config +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Config { + /// The set of config values + pub values: Vec, +} + /// Configuration for a entity #[derive(Clone, Debug, Serialize, Deserialize)] pub struct EntityConfig { diff --git a/digital_twin_adapters/in_memory_mock_digital_twin_adapter/src/in_memory_mock_digital_twin_adapter.rs b/digital_twin_adapters/in_memory_mock_digital_twin_adapter/src/in_memory_mock_digital_twin_adapter.rs index 5a5ca24d..4082f3c0 100644 --- a/digital_twin_adapters/in_memory_mock_digital_twin_adapter/src/in_memory_mock_digital_twin_adapter.rs +++ b/digital_twin_adapters/in_memory_mock_digital_twin_adapter/src/in_memory_mock_digital_twin_adapter.rs @@ -2,48 +2,31 @@ // Licensed under the MIT license. // SPDX-License-Identifier: MIT -use std::{fs, path::Path}; - use async_trait::async_trait; -use crate::config::EntityConfig; +use crate::config::Config; +use freyja_common::{config_utils, out_dir}; use freyja_contracts::digital_twin_adapter::{ DigitalTwinAdapter, DigitalTwinAdapterError, DigitalTwinAdapterErrorKind, GetDigitalTwinProviderRequest, GetDigitalTwinProviderResponse, }; -const CONFIG_FILE: &str = "config.json"; +const CONFIG_FILE_STEM: &str = "in_memory_digital_twin_config"; /// In-memory mock that mocks finding endpoint info about entities /// through find by id pub struct InMemoryMockDigitalTwinAdapter { - /// Stores configs about entities - data: Vec, + /// The adapter config + config: Config, } impl InMemoryMockDigitalTwinAdapter { - /// Creates a new InMemoryMockDigitalTwinAdapter with config from the specified file - /// - /// # Arguments - /// - `config_path`: the path to the config to use - pub fn from_config_file>( - config_path: P, - ) -> Result { - let config_contents = - fs::read_to_string(config_path).map_err(DigitalTwinAdapterError::io)?; - - let config: Vec = serde_json::from_str(config_contents.as_str()) - .map_err(DigitalTwinAdapterError::deserialize)?; - - Self::from_config(config) - } - /// Creates a new InMemoryMockDigitalTwinAdapter with the specified config /// /// # Arguments /// - `config`: the config to use - pub fn from_config(config: Vec) -> Result { - Ok(Self { data: config }) + pub fn from_config(config: Config) -> Result { + Ok(Self { config }) } } @@ -51,7 +34,15 @@ impl InMemoryMockDigitalTwinAdapter { impl DigitalTwinAdapter for InMemoryMockDigitalTwinAdapter { /// Creates a new instance of a DigitalTwinAdapter with default settings fn create_new() -> Result { - Self::from_config_file(Path::new(env!("OUT_DIR")).join(CONFIG_FILE)) + let config = config_utils::read_from_files( + CONFIG_FILE_STEM, + config_utils::JSON_EXT, + out_dir!(), + DigitalTwinAdapterError::io, + DigitalTwinAdapterError::deserialize, + )?; + + Self::from_config(config) } /// Gets the entity information based on the request @@ -62,7 +53,8 @@ impl DigitalTwinAdapter for InMemoryMockDigitalTwinAdapter { &self, request: GetDigitalTwinProviderRequest, ) -> Result { - self.data + self.config + .values .iter() .find(|entity_config| entity_config.entity.id == request.entity_id) .map(|entity_config| GetDigitalTwinProviderResponse { @@ -76,16 +68,11 @@ impl DigitalTwinAdapter for InMemoryMockDigitalTwinAdapter { mod in_memory_mock_digital_twin_adapter_tests { use super::*; + use crate::config::EntityConfig; use freyja_contracts::{entity::Entity, provider_proxy::OperationKind}; #[test] - fn from_config_file_returns_err_on_nonexistent_file() { - let result = InMemoryMockDigitalTwinAdapter::from_config_file("fake_file.foo"); - assert!(result.is_err()); - } - - #[test] - fn can_get_default_config() { + fn can_create_new() { let result = InMemoryMockDigitalTwinAdapter::create_new(); assert!(result.is_ok()); } @@ -94,18 +81,20 @@ mod in_memory_mock_digital_twin_adapter_tests { async fn find_by_id_test() { const ENTITY_ID: &str = "dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1"; - let data = vec![EntityConfig { - entity: Entity { - id: String::from(ENTITY_ID), - name: None, - uri: String::from("http://0.0.0.0:1111"), // Devskim: ignore DS137138 - description: None, - operation: OperationKind::Subscribe, - protocol: String::from("in-memory"), - }, - }]; + let config = Config { + values: vec![EntityConfig { + entity: Entity { + id: String::from(ENTITY_ID), + name: None, + uri: String::from("http://0.0.0.0:1111"), // Devskim: ignore DS137138 + description: None, + operation: OperationKind::Subscribe, + protocol: String::from("in-memory"), + }, + }], + }; - let in_memory_digital_twin_adapter = InMemoryMockDigitalTwinAdapter { data }; + let in_memory_digital_twin_adapter = InMemoryMockDigitalTwinAdapter { config }; let request = GetDigitalTwinProviderRequest { entity_id: String::from(ENTITY_ID), }; diff --git a/digital_twin_adapters/mock_digital_twin_adapter/Cargo.toml b/digital_twin_adapters/mock_digital_twin_adapter/Cargo.toml index a1b31839..150ddccf 100644 --- a/digital_twin_adapters/mock_digital_twin_adapter/Cargo.toml +++ b/digital_twin_adapters/mock_digital_twin_adapter/Cargo.toml @@ -10,8 +10,11 @@ license = "MIT" [dependencies] async-trait = { workspace = true } +freyja-common = { workspace = true } freyja-contracts = { workspace = true } mock-digital-twin = { workspace = true } reqwest = { workspace = true } serde = { workspace = true } -serde_json = { workspace = true } \ No newline at end of file + +[build-dependencies] +freyja-build-common = { workspace = true } \ No newline at end of file diff --git a/digital_twin_adapters/mock_digital_twin_adapter/README.md b/digital_twin_adapters/mock_digital_twin_adapter/README.md index 0708d3e1..4c50ba45 100644 --- a/digital_twin_adapters/mock_digital_twin_adapter/README.md +++ b/digital_twin_adapters/mock_digital_twin_adapter/README.md @@ -4,6 +4,8 @@ The Mock Digital Twin Adapter acts as a client for the [Mock Digital Twin](../.. ## Config -The adapter's config is located at `res/config.json` and will be copied to the build output automatically. This file contains the following properties: +This adapter supports the following configuration settings: -- `base_uri_for_digital_twin_server`: the base uri for the Mock Digital Twin Service +- `digital_twin_service_uri`: the base uri for the Mock Digital Twin Service + +This adapter supports [config overrides](../../docs/config-overrides.md). The override filename is `mock_digital_twin_adapter_config.json`, and the default config is located at `res/mock_digital_twin_adapter_config.default.json`. diff --git a/digital_twin_adapters/mock_digital_twin_adapter/build.rs b/digital_twin_adapters/mock_digital_twin_adapter/build.rs index dc6759ea..aab15d3d 100644 --- a/digital_twin_adapters/mock_digital_twin_adapter/build.rs +++ b/digital_twin_adapters/mock_digital_twin_adapter/build.rs @@ -2,23 +2,19 @@ // Licensed under the MIT license. // SPDX-License-Identifier: MIT -use std::{env, fs, path::Path}; +use std::env; -const OUT_DIR: &str = "OUT_DIR"; -const RESOURCE_DIR: &str = "res"; -const CONFIG_FILE: &str = "config.json"; +use freyja_build_common::copy_to_build_out_dir; + +const RES_DIR_NAME: &str = "res"; +const DEFAULT_CONFIG_FILE: &str = "mock_digital_twin_adapter_config.default.json"; fn main() { // Current directory of the build script is the package's root directory let config_path = env::current_dir() .unwrap() - .join(RESOURCE_DIR) - .join(CONFIG_FILE); - - let target_dir = env::var(OUT_DIR).unwrap(); - let dest_path = Path::new(&target_dir).join(CONFIG_FILE); - - fs::copy(&config_path, dest_path).unwrap(); + .join(RES_DIR_NAME) + .join(DEFAULT_CONFIG_FILE); - println!("cargo:rerun-if-changed={}", config_path.to_str().unwrap()); + copy_to_build_out_dir(config_path, DEFAULT_CONFIG_FILE); } diff --git a/digital_twin_adapters/mock_digital_twin_adapter/res/config.json b/digital_twin_adapters/mock_digital_twin_adapter/res/config.json deleted file mode 100644 index f0264006..00000000 --- a/digital_twin_adapters/mock_digital_twin_adapter/res/config.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "base_uri_for_digital_twin_server": "http://127.0.0.1:8800" -} \ No newline at end of file diff --git a/digital_twin_adapters/mock_digital_twin_adapter/res/mock_digital_twin_adapter_config.default.json b/digital_twin_adapters/mock_digital_twin_adapter/res/mock_digital_twin_adapter_config.default.json new file mode 100644 index 00000000..4786c773 --- /dev/null +++ b/digital_twin_adapters/mock_digital_twin_adapter/res/mock_digital_twin_adapter_config.default.json @@ -0,0 +1,3 @@ +{ + "digital_twin_service_uri": "http://127.0.0.1:8800" +} \ No newline at end of file diff --git a/digital_twin_adapters/mock_digital_twin_adapter/src/config.rs b/digital_twin_adapters/mock_digital_twin_adapter/src/config.rs index de7b755d..30a7bc1f 100644 --- a/digital_twin_adapters/mock_digital_twin_adapter/src/config.rs +++ b/digital_twin_adapters/mock_digital_twin_adapter/src/config.rs @@ -4,11 +4,9 @@ use serde::{Deserialize, Serialize}; -pub const CONFIG_FILE: &str = "config.json"; - -/// Settings for http provider proxy +/// Config for the mock digital twin adapter #[derive(Clone, Debug, Serialize, Deserialize)] -pub(crate) struct Settings { - /// the base uri for the digital twin server - pub base_uri_for_digital_twin_server: String, +pub struct Config { + /// the base uri for the digital twin service + pub digital_twin_service_uri: String, } diff --git a/digital_twin_adapters/mock_digital_twin_adapter/src/mock_digital_twin_adapter.rs b/digital_twin_adapters/mock_digital_twin_adapter/src/mock_digital_twin_adapter.rs index f5ea852f..4fda66f7 100644 --- a/digital_twin_adapters/mock_digital_twin_adapter/src/mock_digital_twin_adapter.rs +++ b/digital_twin_adapters/mock_digital_twin_adapter/src/mock_digital_twin_adapter.rs @@ -2,40 +2,39 @@ // Licensed under the MIT license. // SPDX-License-Identifier: MIT -use std::{fs, path::Path}; - use async_trait::async_trait; +use freyja_common::{config_utils, out_dir}; use reqwest::Client; -use serde_json; -use crate::config::{Settings, CONFIG_FILE}; +use crate::config::Config; use freyja_contracts::digital_twin_adapter::{ DigitalTwinAdapter, DigitalTwinAdapterError, GetDigitalTwinProviderRequest, GetDigitalTwinProviderResponse, }; - use mock_digital_twin::ENTITY_QUERY_PATH; +const CONFIG_FILE_STEM: &str = "mock_digital_twin_adapter_config"; + /// Mocks a Digital Twin Adapter that calls the mocks/mock_digital_twin /// to get entity access info. pub struct MockDigitalTwinAdapter { - /// Base uri for finding an entity's info - base_uri_for_digital_twin_server: String, + /// The adapter config + config: Config, /// Async Reqwest HTTP Client client: Client, } impl MockDigitalTwinAdapter { - /// Creates a new instance of a MockDigitalTwinAdapter + /// Creates a new MockDigitalTwinAdapter with the specified config /// /// # Arguments - /// - `base_uri_for_entity_info`: the base uri for finding entities' access info - pub fn with_uri(base_uri_for_entity_info: &str) -> Self { - Self { - base_uri_for_digital_twin_server: String::from(base_uri_for_entity_info), - client: reqwest::Client::new(), - } + /// - `config`: the config to use + pub fn from_config(config: Config) -> Result { + Ok(Self { + config, + client: Client::new(), + }) } /// Helper to map HTTP error codes to our own error type @@ -56,12 +55,15 @@ impl MockDigitalTwinAdapter { impl DigitalTwinAdapter for MockDigitalTwinAdapter { /// Creates a new instance of a MockDigitalTwinAdapter fn create_new() -> Result { - let settings_content = fs::read_to_string(Path::new(env!("OUT_DIR")).join(CONFIG_FILE)) - .map_err(DigitalTwinAdapterError::io)?; - let settings: Settings = serde_json::from_str(settings_content.as_str()) - .map_err(DigitalTwinAdapterError::deserialize)?; + let config = config_utils::read_from_files( + CONFIG_FILE_STEM, + config_utils::JSON_EXT, + out_dir!(), + DigitalTwinAdapterError::io, + DigitalTwinAdapterError::deserialize, + )?; - Ok(Self::with_uri(&settings.base_uri_for_digital_twin_server)) + Self::from_config(config) } /// Gets the info of an entity via an HTTP request. @@ -74,7 +76,7 @@ impl DigitalTwinAdapter for MockDigitalTwinAdapter { ) -> Result { let target = format!( "{}{ENTITY_QUERY_PATH}{}", - self.base_uri_for_digital_twin_server, request.entity_id + self.config.digital_twin_service_uri, request.entity_id ); self.client diff --git a/mapping_clients/in_memory_mock_mapping_client/Cargo.toml b/mapping_clients/in_memory_mock_mapping_client/Cargo.toml index f45e12c0..826f7a68 100644 --- a/mapping_clients/in_memory_mock_mapping_client/Cargo.toml +++ b/mapping_clients/in_memory_mock_mapping_client/Cargo.toml @@ -13,7 +13,6 @@ async-trait = { workspace = true } freyja-common = { workspace = true } freyja-contracts = { workspace = true } serde = { workspace = true } -serde_json = { workspace = true } tokio = { workspace = true } [build-dependencies] diff --git a/mocks/mock_digital_twin/Cargo.toml b/mocks/mock_digital_twin/Cargo.toml index 474da3f2..af29c3e1 100644 --- a/mocks/mock_digital_twin/Cargo.toml +++ b/mocks/mock_digital_twin/Cargo.toml @@ -11,9 +11,12 @@ license = "MIT" [dependencies] axum = { workspace = true } env_logger = { workspace = true } +freyja-common = { workspace = true } freyja-contracts = { workspace = true } log = { workspace = true } reqwest = { workspace = true } serde = { workspace = true } -serde_json = { workspace = true } -tokio = { workspace = true } \ No newline at end of file +tokio = { workspace = true } + +[build-dependencies] +freyja-build-common = { workspace = true } \ No newline at end of file diff --git a/mocks/mock_digital_twin/README.md b/mocks/mock_digital_twin/README.md index 2dc888a6..b946c448 100644 --- a/mocks/mock_digital_twin/README.md +++ b/mocks/mock_digital_twin/README.md @@ -4,21 +4,22 @@ The Mock Digital Twin mocks the behavior of the in-vehicle digital twin services ## Configuration -The mock can be configured via the `res/config.json` file which is copied to the build output automatically. This file contains the following properties: - -- `digital_twin_server_authority`: the authority that will be used for hosting the mock digital twin service -- `config_items`: a list of objects with the following properties: - - `begin`: an integer indicating when to enable the entity below - - `end`: an optional integer indicating when to disable the entity below. Set to `null` if you never want the entity to "turn off" - - `value`: describes an entity that should be provided via the `find_by_id` API at some point during the application's lifecycle. This object has the following properties: - - `entity`: The properties of this object are identical to those for the [In-Memory Mock Digital Twin Adapter](../../digital_twin_adapters/in_memory_mock_digital_twin_adapter/README.md) with the following modifications: - - `protocol`: When using this service, this value should always be `http` - - `values`: defines the values that the entity's signal should take. This can take one of two forms: - - `Static`: the signal's value is a constant, configured as a string - - `Stepwise`: the signal's value will change, increasing and decreasing cyclically by a set value between and upper and lower bound. When using this setting the following additional configuration is required: - - `start`: the starting value of the signal. This can be either the upper or lower bound. - - `end`: the other bound for the signal value - - `delta`: the amount to add to the signal value at each iteration. If this operation would exceed the specified bounds, then the signal value saturates at the boundary value. +This mock supports the following configuration: + +- `digital_twin_server_authority`: The authority that will be used for hosting the mock digital twin service +- `entities`: A list of entities with the following properties: + - `begin`: An integer indicating when to enable this entity (refer to [Behavior](#behavior) for more information on how this value is used) + - `end`: An optional integer indicating when to disable this entity. Set to `null` if you never want the entity to "turn off" (refer to [Behavior](#behavior) for more information on how this value is used) + - `entity`: Describes an entity that should be provided via the `find_by_id` API at some point during the application's lifecycle. The properties of this object are identical to those of the [In-Memory Mock Digital Twin Adapter](../../digital_twin_adapters/in_memory_mock_digital_twin_adapter/README.md) with the following modifications: + - `protocol`: When using this service, this value should always be `http` + - `values`: Defines the values that the entity's signal should take. This can take one of two forms: + - `Static`: The signal's value is a constant, configured as a string + - `Stepwise`: The signal's value will change, increasing and decreasing cyclically by a set value between and upper and lower bound. When using this setting the following additional configuration is required: + - `start`: The starting value of the signal. This can be either the upper or lower bound. + - `end`: The other bound for the signal value + - `delta`: The amount to add to the signal value at each iteration. If this operation would exceed the specified bounds, then the signal value saturates at the boundary value. + +This mock supports [config overrides](../../docs/config-overrides.md). The override filename is `mock_digital_twin_config.json`, and the default config is located at `res/mock_digital_twin_config.default.json`. ## Behavior diff --git a/mocks/mock_digital_twin/build.rs b/mocks/mock_digital_twin/build.rs index dc6759ea..a4fac96e 100644 --- a/mocks/mock_digital_twin/build.rs +++ b/mocks/mock_digital_twin/build.rs @@ -2,23 +2,19 @@ // Licensed under the MIT license. // SPDX-License-Identifier: MIT -use std::{env, fs, path::Path}; +use std::env; -const OUT_DIR: &str = "OUT_DIR"; -const RESOURCE_DIR: &str = "res"; -const CONFIG_FILE: &str = "config.json"; +use freyja_build_common::copy_to_build_out_dir; + +const RES_DIR_NAME: &str = "res"; +const DEFAULT_CONFIG_FILE: &str = "mock_digital_twin_config.default.json"; fn main() { // Current directory of the build script is the package's root directory let config_path = env::current_dir() .unwrap() - .join(RESOURCE_DIR) - .join(CONFIG_FILE); - - let target_dir = env::var(OUT_DIR).unwrap(); - let dest_path = Path::new(&target_dir).join(CONFIG_FILE); - - fs::copy(&config_path, dest_path).unwrap(); + .join(RES_DIR_NAME) + .join(DEFAULT_CONFIG_FILE); - println!("cargo:rerun-if-changed={}", config_path.to_str().unwrap()); + copy_to_build_out_dir(config_path, DEFAULT_CONFIG_FILE); } diff --git a/mocks/mock_digital_twin/res/config.json b/mocks/mock_digital_twin/res/config.json deleted file mode 100644 index c6df279a..00000000 --- a/mocks/mock_digital_twin/res/config.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "digital_twin_server_authority": "127.0.0.1:8800", - "config_items": [ - { - "begin": 1, - "end": null, - "value": { - "entity": { - "id": "dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1", - "name": "AmbientAirTemperature", - "uri": "http://127.0.0.1:8800", - "description": "The immediate surroundings air temperature (in Fahrenheit).", - "operation": "Get", - "protocol": "http" - }, - "values": { - "Static": "42.0" - } - } - }, - { - "begin": 2, - "end": null, - "value": { - "entity": { - "id": "dtmi:sdv:Vehicle:Cabin:HVAC:IsAirConditioningActive;1", - "name": "IsAirConditioningActive", - "uri": "http://127.0.0.1:8800", - "description": "Is air conditioning active?", - "operation": "Get", - "protocol": "http" - }, - "values": { - "Static": "true" - } - } - }, - { - "begin": 3, - "end": null, - "value": { - "entity": { - "id": "dtmi:sdv:Vehicle:OBD:HybridBatteryRemaining;1", - "name": "HybridBatteryRemaining", - "uri": "http://127.0.0.1:8800", - "description": "Percentage of the hybrid battery remaining", - "operation": "Subscribe", - "protocol": "http" - }, - "values": { - "Stepwise": { - "start": 77.7, - "end": 0.0, - "delta": -0.125 - } - } - } - } - ] -} diff --git a/mocks/mock_digital_twin/res/mock_digital_twin_config.default.json b/mocks/mock_digital_twin/res/mock_digital_twin_config.default.json new file mode 100644 index 00000000..b728e712 --- /dev/null +++ b/mocks/mock_digital_twin/res/mock_digital_twin_config.default.json @@ -0,0 +1,54 @@ +{ + "digital_twin_server_authority": "127.0.0.1:8800", + "entities": [ + { + "begin": 1, + "end": null, + "entity": { + "id": "dtmi:sdv:Vehicle:Cabin:HVAC:AmbientAirTemperature;1", + "name": "AmbientAirTemperature", + "uri": "http://127.0.0.1:8800", + "description": "The immediate surroundings air temperature (in Fahrenheit).", + "operation": "Get", + "protocol": "http" + }, + "values": { + "Static": "42.0" + } + }, + { + "begin": 2, + "end": null, + "entity": { + "id": "dtmi:sdv:Vehicle:Cabin:HVAC:IsAirConditioningActive;1", + "name": "IsAirConditioningActive", + "uri": "http://127.0.0.1:8800", + "description": "Is air conditioning active?", + "operation": "Get", + "protocol": "http" + }, + "values": { + "Static": "true" + } + }, + { + "begin": 3, + "end": null, + "entity": { + "id": "dtmi:sdv:Vehicle:OBD:HybridBatteryRemaining;1", + "name": "HybridBatteryRemaining", + "uri": "http://127.0.0.1:8800", + "description": "Percentage of the hybrid battery remaining", + "operation": "Subscribe", + "protocol": "http" + }, + "values": { + "Stepwise": { + "start": 77.7, + "end": 0.0, + "delta": -0.125 + } + } + } + ] +} diff --git a/mocks/mock_digital_twin/src/config.rs b/mocks/mock_digital_twin/src/config.rs index f35674bf..af02c843 100644 --- a/mocks/mock_digital_twin/src/config.rs +++ b/mocks/mock_digital_twin/src/config.rs @@ -6,35 +6,26 @@ use serde::{Deserialize, Serialize}; use freyja_contracts::entity::Entity; -pub(crate) const CONFIG_FILE: &str = "config.json"; - -/// Settings for mock digital twin +/// Config for the mock digital twin #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct Settings { - /// the digital twin server authority for hosting a gRPC server +pub struct Config { + /// The digital twin server authority for hosting a gRPC server pub digital_twin_server_authority: String, - /// the config items - pub config_items: Vec, + /// The list of entities + pub entities: Vec, } /// A config entry for the MockDigitalTwinAdapter #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct ConfigItem { +pub struct EntityConfig { /// Start emitting the value after this many calls to the client pub begin: u8, /// Stop emitting the value after this many calls to the client (or don't stop emitting if None) pub end: Option, - /// The config for the provider - pub value: ProviderConfig, -} - -/// Configuration for a mock provider -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct ProviderConfig { - // The provider's entity + /// The entity to provide pub entity: Entity, /// The config for the sensor values diff --git a/mocks/mock_digital_twin/src/main.rs b/mocks/mock_digital_twin/src/main.rs index 7df06f2b..64cb7afc 100644 --- a/mocks/mock_digital_twin/src/main.rs +++ b/mocks/mock_digital_twin/src/main.rs @@ -6,24 +6,25 @@ mod config; use std::collections::{HashMap, HashSet}; use std::sync::{Arc, Mutex}; -use std::{fs, io, net::SocketAddr, path::Path, thread, time::Duration}; +use std::{io, net::SocketAddr, thread, time::Duration}; use axum::response::{IntoResponse, Response}; use axum::routing::{get, post}; use axum::{extract, extract::State, Json, Router, Server}; use env_logger::Target; +use freyja_common::{config_utils, out_dir}; use log::{debug, error, info, warn, LevelFilter}; use reqwest::Client; use serde::Deserialize; use tokio::sync::{mpsc, mpsc::UnboundedSender}; -use config::{ConfigItem, Settings, CONFIG_FILE}; +use crate::config::{Config, EntityConfig}; use freyja_contracts::digital_twin_adapter::{ EntityValueRequest, EntityValueResponse, GetDigitalTwinProviderResponse, }; -use freyja_contracts::entity::Entity; use mock_digital_twin::{ENTITY_GET_VALUE_PATH, ENTITY_PATH, ENTITY_SUBSCRIBE_PATH}; +const CONFIG_FILE_STEM: &str = "mock_digital_twin_config"; const GET_OPERATION: &str = "Get"; const SUBSCRIBE_OPERATION: &str = "Subscribe"; @@ -31,7 +32,7 @@ const SUBSCRIBE_OPERATION: &str = "Subscribe"; /// for getting/subscribing to an entity. struct DigitalTwinAdapterState { count: u8, - entities: Vec<(ConfigItem, u8)>, + entities: Vec<(EntityConfig, u8)>, subscriptions: HashMap>, response_channel_sender: UnboundedSender<(String, EntityValueResponse)>, } @@ -90,19 +91,24 @@ async fn main() { .target(Target::Stdout) .init(); - let settings_content = - fs::read_to_string(Path::new(env!("OUT_DIR")).join(CONFIG_FILE)).unwrap(); - let settings: Settings = serde_json::from_str(settings_content.as_str()).unwrap(); - let config_items = settings.config_items; + let config: Config = config_utils::read_from_files( + CONFIG_FILE_STEM, + config_utils::JSON_EXT, + out_dir!(), + |e| log::error!("{}", e), + |e| log::error!("{}", e), + ) + .unwrap(); let (sender, mut receiver) = mpsc::unbounded_channel::<(String, EntityValueResponse)>(); let state = Arc::new(Mutex::new(DigitalTwinAdapterState { count: 0, - entities: config_items.iter().map(|c| (c.clone(), 0)).collect(), - subscriptions: config_items + entities: config.entities.iter().map(|c| (c.clone(), 0)).collect(), + subscriptions: config + .entities .iter() - .map(|c| (c.value.entity.id.clone(), HashSet::new())) + .map(|c| (c.entity.id.clone(), HashSet::new())) .collect(), response_channel_sender: sender, })); @@ -216,7 +222,7 @@ async fn main() { // HTTP server setup info!( "Mock Digital Twin Adapter Server starting at {}", - settings.digital_twin_server_authority + config.digital_twin_server_authority ); let app = Router::new() @@ -226,7 +232,7 @@ async fn main() { .with_state(state); Server::bind( - &settings + &config .digital_twin_server_authority .parse::() .expect("unable to parse socket address"), @@ -249,24 +255,18 @@ async fn get_entity( let state = state.lock().unwrap(); find_entity(&state, &query.id) .map(|(config_item, _)| { - let operation_path = - if config_item.value.entity.operation.to_string() == SUBSCRIBE_OPERATION { - ENTITY_SUBSCRIBE_PATH - } else if config_item.value.entity.operation.to_string() == GET_OPERATION { - ENTITY_GET_VALUE_PATH - } else { - return server_error!("Entity didn't have a valid operation"); - }; - - let entity = Entity { - id: config_item.value.entity.id.clone(), - name: config_item.value.entity.name.clone(), - uri: format!("{}{operation_path}", config_item.value.entity.uri), - description: config_item.value.entity.description.clone(), - operation: config_item.value.entity.operation.clone(), - protocol: config_item.value.entity.protocol.clone(), + let operation_path = if config_item.entity.operation.to_string() == SUBSCRIBE_OPERATION + { + ENTITY_SUBSCRIBE_PATH + } else if config_item.entity.operation.to_string() == GET_OPERATION { + ENTITY_GET_VALUE_PATH + } else { + return server_error!("Entity didn't have a valid operation"); }; + let mut entity = config_item.entity.clone(); + entity.uri = format!("{}{operation_path}", config_item.entity.uri); + ok!(GetDigitalTwinProviderResponse { entity }) }) .unwrap_or(not_found!()) @@ -354,11 +354,10 @@ fn get_active_entity_names(state: &DigitalTwinAdapterState) -> Vec { if within_bounds(state.count, config_item.begin, config_item.end) { Some( config_item - .value .entity .name .clone() - .unwrap_or_else(|| config_item.value.entity.id.clone()), + .unwrap_or_else(|| config_item.entity.id.clone()), ) } else { None @@ -375,12 +374,12 @@ fn get_active_entity_names(state: &DigitalTwinAdapterState) -> Vec { fn find_entity<'a>( state: &'a DigitalTwinAdapterState, id: &'a String, -) -> Option<&'a (ConfigItem, u8)> { +) -> Option<&'a (EntityConfig, u8)> { state .entities .iter() .filter(|(config_item, _)| within_bounds(state.count, config_item.begin, config_item.end)) - .find(|(config_item, _)| config_item.value.entity.id == *id) + .find(|(config_item, _)| config_item.entity.id == *id) } /// Gets an entity's value @@ -394,9 +393,9 @@ fn get_entity_value(state: &mut DigitalTwinAdapterState, id: &str) -> Option