mockforge_mqtt/
fixtures.rs1use serde::{Deserialize, Serialize};
2use std::collections::HashMap;
3
4#[derive(Debug, Clone, Serialize, Deserialize)]
6pub struct MqttFixture {
7 pub identifier: String,
8 pub name: String,
9 pub topic_pattern: String, pub qos: u8,
11 pub retained: bool,
12 pub response: MqttResponse,
13 pub auto_publish: Option<AutoPublishConfig>,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize)]
18pub struct MqttResponse {
19 pub payload: serde_json::Value, }
21
22#[derive(Debug, Clone, Serialize, Deserialize)]
24pub struct AutoPublishConfig {
25 pub enabled: bool,
26 pub interval_ms: u64,
27 pub count: Option<usize>, }
29
30pub struct MqttFixtureRegistry {
32 fixtures: HashMap<String, MqttFixture>,
33}
34
35impl Default for MqttFixtureRegistry {
36 fn default() -> Self {
37 Self::new()
38 }
39}
40
41impl MqttFixtureRegistry {
42 pub fn new() -> Self {
43 Self {
44 fixtures: HashMap::new(),
45 }
46 }
47
48 pub fn add_fixture(&mut self, fixture: MqttFixture) {
49 self.fixtures.insert(fixture.identifier.clone(), fixture);
50 }
51
52 pub fn get_fixture(&self, identifier: &str) -> Option<&MqttFixture> {
53 self.fixtures.get(identifier)
54 }
55
56 pub fn find_by_topic(&self, topic: &str) -> Option<&MqttFixture> {
57 for fixture in self.fixtures.values() {
58 if regex::Regex::new(&fixture.topic_pattern).ok()?.is_match(topic) {
59 return Some(fixture);
60 }
61 }
62 None
63 }
64
65 pub fn fixtures(&self) -> impl Iterator<Item = &MqttFixture> {
66 self.fixtures.values()
67 }
68
69 pub fn load_from_directory(
71 &mut self,
72 path: &std::path::Path,
73 ) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
74 if !path.exists() {
75 return Err(format!("Fixtures directory does not exist: {}", path.display()).into());
76 }
77
78 if !path.is_dir() {
79 return Err(format!("Path is not a directory: {}", path.display()).into());
80 }
81
82 let mut loaded_count = 0;
83
84 for entry in std::fs::read_dir(path)? {
86 let entry = entry?;
87 let path = entry.path();
88
89 if path.is_file() {
90 if let Some(extension) = path.extension() {
91 if extension == "json" || extension == "yaml" || extension == "yml" {
92 match self.load_fixture_file(&path) {
93 Ok(fixture) => {
94 self.add_fixture(fixture);
95 loaded_count += 1;
96 }
97 Err(e) => {
98 eprintln!(
99 "Warning: Failed to load fixture from {}: {}",
100 path.display(),
101 e
102 );
103 }
104 }
105 }
106 }
107 }
108 }
109
110 println!("✅ Loaded {} MQTT fixtures from {}", loaded_count, path.display());
111 Ok(())
112 }
113
114 fn load_fixture_file(
116 &self,
117 path: &std::path::Path,
118 ) -> Result<MqttFixture, Box<dyn std::error::Error + Send + Sync>> {
119 let content = std::fs::read_to_string(path)?;
120 let fixture: MqttFixture = if path.extension().unwrap_or_default() == "json" {
121 serde_json::from_str(&content)?
122 } else {
123 serde_yaml::from_str(&content)?
124 };
125 Ok(fixture)
126 }
127}