Skip to content

Commit 823e18a

Browse files
feat: Update mock source config to support DataType enum with sensorCount
- Add DataTypeDto enum with Counter, SensorReading, and Generic variants - Support both string ('sensor_reading') and object ({ type: 'sensor_reading', sensor_count: 10 }) formats for dataType - Update MockSourceConfigMapper to convert DataTypeDto to domain DataType - Update interactive prompts to let users choose data type and sensor count - Update README with new mock source configuration documentation - Update config/server-docker.yaml to use valid data types - Add drasi-core and drasi-lib path patches for local development Breaking change: dataType field is now a DataTypeDto enum instead of ConfigValue<String>. Backward compatible: string values still work for simple cases.
1 parent 95631ba commit 823e18a

14 files changed

Lines changed: 254 additions & 58 deletions

File tree

README.md

Lines changed: 31 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -349,22 +349,49 @@ sources:
349349

350350
#### Mock Source (`mock`)
351351

352-
Generates test data for development.
352+
Generates synthetic test data for development and demonstrations. Supports three data types with configurable generation intervals.
353353

354+
**Simple string format:**
354355
```yaml
355356
sources:
356357
- kind: mock
357358
id: test-source
358359
autoStart: true
359-
dataType: sensor
360-
intervalMs: 5000
360+
dataType: sensor_reading # or "counter", "generic"
361+
intervalMs: 2000
362+
```
363+
364+
**Object format with sensor count:**
365+
```yaml
366+
sources:
367+
- kind: mock
368+
id: sensor-source
369+
autoStart: true
370+
dataType:
371+
type: sensor_reading
372+
sensor_count: 10 # Simulate 10 unique sensors
373+
intervalMs: 2000
361374
```
362375

363376
| Field | Type | Default | Description |
364377
|-------|------|---------|-------------|
365-
| `dataType` | string | `generic` | Type of mock data: `sensor` (SensorReading nodes), `counter` (Counter nodes), `generic` (Generic nodes) |
378+
| `dataType` | string or object | `generic` | Type of mock data (see below) |
366379
| `intervalMs` | integer | `5000` | Data generation interval in milliseconds |
367380

381+
**Data Types:**
382+
383+
| Type | String Value | Generated Nodes | Properties |
384+
|------|--------------|-----------------|------------|
385+
| Counter | `counter` | `Counter` | `value` (sequential int), `timestamp` |
386+
| Sensor Reading | `sensor_reading` or `sensor` | `SensorReading` | `sensor_id`, `temperature` (20-30°C), `humidity` (40-60%), `timestamp` |
387+
| Generic | `generic` | `Generic` | `value` (random int), `message`, `timestamp` |
388+
389+
**Sensor Reading Behavior:**
390+
- First reading for each sensor generates an **INSERT** event
391+
- Subsequent readings for the same sensor generate **UPDATE** events
392+
- `sensor_count` controls how many unique sensors are simulated (default: 5)
393+
- Sensor IDs: `sensor_0` through `sensor_{sensor_count-1}`
394+
368395
#### Platform Source (`platform`)
369396

370397
Consumes events from Redis Streams for Drasi Platform integration.

config/server-docker.yaml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,14 @@ sources:
1313
autoStart: true
1414
kind: mock
1515
intervalMs: 5000
16-
dataType: vehicle_location
16+
dataType:
17+
type: sensor_reading
18+
sensor_count: 10
1719

1820
- id: order-status-source
1921
autoStart: true
2022
kind: mock
21-
dataType: order_status
23+
dataType: generic
2224
intervalMs: 3000
2325

2426
queries:

src/api/mappings/sources/mock_mapper.rs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,9 @@
1515
//! Mock source configuration mapper.
1616
1717
use crate::api::mappings::{ConfigMapper, DtoMapper, MappingError};
18+
use crate::api::models::sources::mock::DataTypeDto;
1819
use crate::api::models::MockSourceConfigDto;
19-
use drasi_source_mock::MockSourceConfig;
20+
use drasi_source_mock::{DataType, MockSourceConfig};
2021

2122
pub struct MockSourceConfigMapper;
2223

@@ -26,8 +27,17 @@ impl ConfigMapper<MockSourceConfigDto, MockSourceConfig> for MockSourceConfigMap
2627
dto: &MockSourceConfigDto,
2728
resolver: &DtoMapper,
2829
) -> Result<MockSourceConfig, MappingError> {
30+
// Map DataTypeDto to DataType
31+
let data_type = match &dto.data_type {
32+
DataTypeDto::Counter => DataType::Counter,
33+
DataTypeDto::SensorReading { sensor_count } => DataType::SensorReading {
34+
sensor_count: *sensor_count,
35+
},
36+
DataTypeDto::Generic => DataType::Generic,
37+
};
38+
2939
Ok(MockSourceConfig {
30-
data_type: resolver.resolve_string(&dto.data_type)?,
40+
data_type,
3141
interval_ms: resolver.resolve_typed(&dto.interval_ms)?,
3242
})
3343
}

src/api/models/mod.rs

Lines changed: 7 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1399,7 +1399,7 @@ autoStart: true
13991399
auto_start: false,
14001400
bootstrap_provider: None,
14011401
config: MockSourceConfigDto {
1402-
data_type: ConfigValue::Static("sensor".to_string()),
1402+
data_type: DataTypeDto::SensorReading { sensor_count: 5 },
14031403
interval_ms: ConfigValue::Static(1000),
14041404
},
14051405
};
@@ -1451,26 +1451,21 @@ autoStart: true
14511451
}
14521452

14531453
#[test]
1454-
fn test_source_deserialize_with_env_var_syntax() {
1454+
fn test_source_deserialize_with_string_data_type() {
14551455
let json = r#"{
14561456
"kind": "mock",
14571457
"id": "test-source",
1458-
"dataType": "${DATA_TYPE:-sensor}",
1458+
"dataType": "sensor_reading",
14591459
"intervalMs": 1000
14601460
}"#;
14611461

14621462
let source: SourceConfig = serde_json::from_str(json).unwrap();
14631463
assert_eq!(source.id(), "test-source");
1464-
// ConfigValue parses env var syntax into EnvironmentVariable variant
14651464
if let SourceConfig::Mock { config, .. } = source {
1466-
assert!(
1467-
matches!(
1468-
&config.data_type,
1469-
ConfigValue::EnvironmentVariable { name, default }
1470-
if name == "DATA_TYPE" && *default == Some("sensor".to_string())
1471-
),
1472-
"Expected EnvironmentVariable variant, got {:?}",
1473-
config.data_type
1465+
assert_eq!(
1466+
config.data_type,
1467+
mock::DataTypeDto::SensorReading { sensor_count: 5 },
1468+
"Expected SensorReading data type"
14741469
);
14751470
}
14761471
}

src/api/models/sources/mock.rs

Lines changed: 142 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,36 +17,106 @@
1717
use crate::api::models::ConfigValue;
1818
use serde::{Deserialize, Serialize};
1919

20+
fn default_sensor_count() -> u32 {
21+
5
22+
}
23+
24+
/// Type of data to generate from the mock source.
25+
///
26+
/// This mirrors the `DataType` enum from drasi-source-mock but uses
27+
/// serde-compatible serialization for YAML configuration files.
28+
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
29+
#[serde(tag = "type", rename_all = "snake_case")]
30+
pub enum DataTypeDto {
31+
/// Sequential counter values (Counter nodes)
32+
Counter,
33+
/// Simulated sensor readings with temperature and humidity (SensorReading nodes)
34+
/// First reading for each sensor generates INSERT, subsequent readings generate UPDATE
35+
SensorReading {
36+
/// Number of sensors to simulate (default: 5)
37+
#[serde(default = "default_sensor_count")]
38+
sensor_count: u32,
39+
},
40+
/// Generic random data (Generic nodes) - default mode
41+
#[default]
42+
Generic,
43+
}
44+
2045
/// Local copy of mock source configuration
2146
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2247
#[serde(rename_all = "camelCase", deny_unknown_fields)]
2348
pub struct MockSourceConfigDto {
24-
#[serde(default = "default_data_type")]
25-
pub data_type: ConfigValue<String>,
49+
/// Type of data to generate. Can be specified as:
50+
/// - Simple string: "counter", "sensor_reading" (or "sensor"), "generic"
51+
/// - Object with type and options: { type: "sensor_reading", sensor_count: 10 }
52+
#[serde(default, deserialize_with = "deserialize_data_type")]
53+
pub data_type: DataTypeDto,
54+
/// Interval between data generation events in milliseconds
2655
#[serde(default = "default_interval_ms")]
2756
pub interval_ms: ConfigValue<u64>,
2857
}
2958

30-
fn default_data_type() -> ConfigValue<String> {
31-
ConfigValue::Static("generic".to_string())
32-
}
33-
3459
fn default_interval_ms() -> ConfigValue<u64> {
3560
ConfigValue::Static(5000)
3661
}
3762

63+
/// Custom deserializer that accepts either a string or an object for data_type
64+
fn deserialize_data_type<'de, D>(deserializer: D) -> Result<DataTypeDto, D::Error>
65+
where
66+
D: serde::Deserializer<'de>,
67+
{
68+
use serde::de::{self, Visitor};
69+
use std::fmt;
70+
71+
struct DataTypeVisitor;
72+
73+
impl<'de> Visitor<'de> for DataTypeVisitor {
74+
type Value = DataTypeDto;
75+
76+
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
77+
formatter.write_str("a string like 'counter', 'sensor_reading', 'generic' or an object with 'type' field")
78+
}
79+
80+
fn visit_str<E>(self, value: &str) -> Result<DataTypeDto, E>
81+
where
82+
E: de::Error,
83+
{
84+
match value.to_lowercase().as_str() {
85+
"counter" => Ok(DataTypeDto::Counter),
86+
"sensor_reading" | "sensor" => Ok(DataTypeDto::SensorReading {
87+
sensor_count: default_sensor_count(),
88+
}),
89+
"generic" => Ok(DataTypeDto::Generic),
90+
_ => Err(de::Error::custom(format!(
91+
"Invalid data_type '{value}'. Valid options are: counter, sensor_reading, generic"
92+
))),
93+
}
94+
}
95+
96+
fn visit_map<M>(self, map: M) -> Result<DataTypeDto, M::Error>
97+
where
98+
M: de::MapAccess<'de>,
99+
{
100+
// Delegate to the default tagged enum deserializer
101+
Deserialize::deserialize(de::value::MapAccessDeserializer::new(map))
102+
}
103+
}
104+
105+
deserializer.deserialize_any(DataTypeVisitor)
106+
}
107+
38108
#[cfg(test)]
39109
mod tests {
40110
use super::*;
41111
use crate::api::models::SourceConfig;
42112

43113
#[test]
44-
fn test_mock_source_config_deserializes_camelcase() {
114+
fn test_mock_source_config_deserializes_string_data_type() {
45115
let yaml = r#"
46116
kind: mock
47117
id: test-source
48118
autoStart: true
49-
dataType: "sensor_live"
119+
dataType: "sensor_reading"
50120
intervalMs: 3000
51121
"#;
52122

@@ -62,14 +132,76 @@ intervalMs: 3000
62132
assert!(auto_start);
63133
assert_eq!(
64134
config.data_type,
65-
ConfigValue::Static("sensor_live".to_string())
135+
DataTypeDto::SensorReading { sensor_count: 5 }
66136
);
67137
assert_eq!(config.interval_ms, ConfigValue::Static(3000));
68138
}
69139
_ => panic!("Expected Mock variant"),
70140
}
71141
}
72142

143+
#[test]
144+
fn test_mock_source_config_deserializes_sensor_legacy_name() {
145+
// Test backwards compatibility with "sensor" (legacy name for "sensor_reading")
146+
let yaml = r#"
147+
kind: mock
148+
id: test-source
149+
dataType: "sensor"
150+
"#;
151+
152+
let config: SourceConfig = serde_yaml::from_str(yaml).expect("Failed to parse YAML");
153+
match config {
154+
SourceConfig::Mock { config, .. } => {
155+
assert_eq!(
156+
config.data_type,
157+
DataTypeDto::SensorReading { sensor_count: 5 }
158+
);
159+
}
160+
_ => panic!("Expected Mock variant"),
161+
}
162+
}
163+
164+
#[test]
165+
fn test_mock_source_config_deserializes_object_data_type() {
166+
let yaml = r#"
167+
kind: mock
168+
id: test-source
169+
dataType:
170+
type: sensor_reading
171+
sensor_count: 10
172+
intervalMs: 2000
173+
"#;
174+
175+
let config: SourceConfig = serde_yaml::from_str(yaml).expect("Failed to parse YAML");
176+
match config {
177+
SourceConfig::Mock { config, .. } => {
178+
assert_eq!(
179+
config.data_type,
180+
DataTypeDto::SensorReading { sensor_count: 10 }
181+
);
182+
assert_eq!(config.interval_ms, ConfigValue::Static(2000));
183+
}
184+
_ => panic!("Expected Mock variant"),
185+
}
186+
}
187+
188+
#[test]
189+
fn test_mock_source_config_counter_type() {
190+
let yaml = r#"
191+
kind: mock
192+
id: counter-source
193+
dataType: "counter"
194+
"#;
195+
196+
let config: SourceConfig = serde_yaml::from_str(yaml).expect("Failed to parse YAML");
197+
match config {
198+
SourceConfig::Mock { config, .. } => {
199+
assert_eq!(config.data_type, DataTypeDto::Counter);
200+
}
201+
_ => panic!("Expected Mock variant"),
202+
}
203+
}
204+
73205
#[test]
74206
fn test_mock_source_config_uses_defaults() {
75207
let yaml = r#"
@@ -87,7 +219,7 @@ id: default-source
87219
} => {
88220
assert_eq!(id, "default-source");
89221
assert!(auto_start, "auto_start should default to true");
90-
assert_eq!(config.data_type, ConfigValue::Static("generic".to_string()));
222+
assert_eq!(config.data_type, DataTypeDto::Generic);
91223
assert_eq!(config.interval_ms, ConfigValue::Static(5000));
92224
}
93225
_ => panic!("Expected Mock variant"),

0 commit comments

Comments
 (0)