Skip to main content

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}