drasi_source_mock/config.rs
1// Copyright 2025 The Drasi Authors.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//! Configuration types for the mock source.
16//!
17//! This module defines the configuration options that control how the mock source
18//! generates synthetic data.
19
20use serde::{Deserialize, Serialize};
21use std::fmt;
22
23fn default_sensor_count() -> u32 {
24 5
25}
26
27/// Specifies the type of synthetic data to generate.
28///
29/// Each variant produces elements with different schemas and behaviors.
30/// See the [crate-level documentation](crate) for detailed examples of each type.
31#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, Default)]
32#[serde(tag = "type", rename_all = "snake_case")]
33pub enum DataType {
34 /// Generates sequential counter values (1, 2, 3, ...).
35 ///
36 /// Each event is an INSERT with a unique element ID (`counter_1`, `counter_2`, etc.).
37 /// Properties include `value` (the counter) and `timestamp`.
38 Counter,
39
40 /// Generates simulated IoT sensor readings.
41 ///
42 /// Produces temperature (20.0-30.0) and humidity (40.0-60.0) values from
43 /// a pool of simulated sensors. The first reading for each sensor is an INSERT;
44 /// subsequent readings for the same sensor are UPDATEs.
45 SensorReading {
46 /// Number of distinct sensors to simulate. Must be at least 1.
47 /// Sensor IDs will be in range `[0, sensor_count)`.
48 #[serde(default = "default_sensor_count")]
49 sensor_count: u32,
50 },
51
52 /// Generates generic data with random values (default).
53 ///
54 /// Each event is an INSERT with a unique element ID (`generic_1`, `generic_2`, etc.).
55 /// Properties include `value` (random i32), `message`, and `timestamp`.
56 #[default]
57 Generic,
58}
59
60impl DataType {
61 /// Creates a [`SensorReading`](DataType::SensorReading) data type with the specified sensor count.
62 ///
63 /// # Arguments
64 ///
65 /// * `sensor_count` - Number of distinct sensors to simulate (must be ≥ 1)
66 ///
67 /// # Example
68 ///
69 /// ```rust
70 /// use drasi_source_mock::DataType;
71 ///
72 /// let data_type = DataType::sensor_reading(10);
73 /// assert_eq!(data_type.sensor_count(), Some(10));
74 /// ```
75 pub fn sensor_reading(sensor_count: u32) -> Self {
76 DataType::SensorReading { sensor_count }
77 }
78
79 /// Returns the sensor count if this is a [`SensorReading`](DataType::SensorReading) variant.
80 ///
81 /// # Returns
82 ///
83 /// - `Some(count)` for `SensorReading` variants
84 /// - `None` for `Counter` and `Generic` variants
85 pub fn sensor_count(&self) -> Option<u32> {
86 match self {
87 DataType::SensorReading { sensor_count } => Some(*sensor_count),
88 _ => None,
89 }
90 }
91}
92
93impl fmt::Display for DataType {
94 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
95 match self {
96 DataType::Counter => write!(f, "counter"),
97 DataType::SensorReading { .. } => write!(f, "sensor_reading"),
98 DataType::Generic => write!(f, "generic"),
99 }
100 }
101}
102
103/// Configuration for a [`MockSource`](crate::MockSource) instance.
104///
105/// Controls what type of data is generated and how frequently.
106///
107/// # Example
108///
109/// ```rust
110/// use drasi_source_mock::{MockSourceConfig, DataType};
111///
112/// let config = MockSourceConfig {
113/// data_type: DataType::sensor_reading(10),
114/// interval_ms: 1000,
115/// };
116/// ```
117///
118/// # Serialization
119///
120/// This type supports serde serialization for configuration files:
121///
122/// ```yaml
123/// data_type:
124/// type: sensor_reading
125/// sensor_count: 10
126/// interval_ms: 1000
127/// ```
128#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
129pub struct MockSourceConfig {
130 /// The type of synthetic data to generate.
131 ///
132 /// Defaults to [`DataType::Generic`] if not specified.
133 #[serde(default)]
134 pub data_type: DataType,
135
136 /// Interval between generated events in milliseconds.
137 ///
138 /// Must be greater than 0. Defaults to 5000 (5 seconds) if not specified.
139 #[serde(default = "default_interval_ms")]
140 pub interval_ms: u64,
141}
142
143fn default_interval_ms() -> u64 {
144 5000
145}
146
147impl Default for MockSourceConfig {
148 fn default() -> Self {
149 Self {
150 data_type: DataType::default(),
151 interval_ms: default_interval_ms(),
152 }
153 }
154}
155
156impl MockSourceConfig {
157 /// Validates this configuration.
158 ///
159 /// # Errors
160 ///
161 /// Returns [`anyhow::Error`] if:
162 /// - `interval_ms` is 0 (would cause a spin loop with no delay between events)
163 /// - `data_type` is `SensorReading` with `sensor_count` of 0 (must have at least one sensor)
164 ///
165 /// # Example
166 ///
167 /// ```rust
168 /// use drasi_source_mock::{MockSourceConfig, DataType};
169 ///
170 /// let valid_config = MockSourceConfig {
171 /// data_type: DataType::Counter,
172 /// interval_ms: 1000,
173 /// };
174 /// assert!(valid_config.validate().is_ok());
175 ///
176 /// let invalid_config = MockSourceConfig {
177 /// data_type: DataType::Counter,
178 /// interval_ms: 0, // Invalid!
179 /// };
180 /// assert!(invalid_config.validate().is_err());
181 /// ```
182 pub fn validate(&self) -> anyhow::Result<()> {
183 if self.interval_ms == 0 {
184 return Err(anyhow::anyhow!(
185 "Validation error: interval_ms cannot be 0. \
186 Please specify a positive interval in milliseconds (minimum 1)"
187 ));
188 }
189
190 if let DataType::SensorReading { sensor_count } = &self.data_type {
191 if *sensor_count == 0 {
192 return Err(anyhow::anyhow!(
193 "Validation error: sensor_count cannot be 0. \
194 Please specify at least 1 sensor"
195 ));
196 }
197 }
198
199 Ok(())
200 }
201}