1717use crate :: api:: models:: ConfigValue ;
1818use 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) ]
2348pub 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-
3459fn 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) ]
39109mod 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#"
46116kind: mock
47117id: test-source
48118autoStart: true
49- dataType: "sensor_live "
119+ dataType: "sensor_reading "
50120intervalMs: 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