mockforge_tcp/
spec_registry.rs1use crate::fixtures::TcpFixture;
4use mockforge_core::Result;
5use std::collections::HashMap;
6use std::path::Path;
7use tracing::{debug, info, warn};
8
9#[derive(Debug, Clone)]
11pub struct TcpSpecRegistry {
12 fixtures: HashMap<String, TcpFixture>,
13}
14
15impl TcpSpecRegistry {
16 pub fn new() -> Self {
18 Self {
19 fixtures: HashMap::new(),
20 }
21 }
22
23 pub fn load_fixtures<P: AsRef<Path>>(&mut self, fixtures_dir: P) -> Result<()> {
25 let fixtures_dir = fixtures_dir.as_ref();
26 if !fixtures_dir.exists() {
27 debug!("TCP fixtures directory does not exist: {:?}", fixtures_dir);
28 return Ok(());
29 }
30
31 info!("Loading TCP fixtures from {:?}", fixtures_dir);
32
33 let entries = std::fs::read_dir(fixtures_dir).map_err(|e| {
34 mockforge_core::Error::generic(format!("Failed to read fixtures directory: {}", e))
35 })?;
36
37 let mut loaded_count = 0;
38
39 for entry in entries {
40 let entry = entry.map_err(|e| {
41 mockforge_core::Error::generic(format!("Failed to read directory entry: {}", e))
42 })?;
43 let path = entry.path();
44
45 if path.is_file() {
46 match path.extension().and_then(|s| s.to_str()) {
47 Some("yaml") | Some("yml") | Some("json") => {
48 if let Err(e) = self.load_fixture_file(&path) {
49 warn!("Failed to load fixture from {:?}: {}", path, e);
50 } else {
51 loaded_count += 1;
52 }
53 }
54 _ => {
55 debug!("Skipping non-fixture file: {:?}", path);
56 }
57 }
58 }
59 }
60
61 info!("Loaded {} TCP fixture(s)", loaded_count);
62 Ok(())
63 }
64
65 fn load_fixture_file<P: AsRef<Path>>(&mut self, path: P) -> Result<()> {
67 let path = path.as_ref();
68 let content = std::fs::read_to_string(path).map_err(|e| {
69 mockforge_core::Error::generic(format!("Failed to read fixture file: {}", e))
70 })?;
71
72 let fixtures: Vec<TcpFixture> = if path.extension().and_then(|s| s.to_str()) == Some("json")
73 {
74 serde_json::from_str(&content).map_err(|e| {
75 mockforge_core::Error::generic(format!("Failed to parse JSON fixture: {}", e))
76 })?
77 } else {
78 serde_yaml::from_str(&content).map_err(|e| {
79 mockforge_core::Error::generic(format!("Failed to parse YAML fixture: {}", e))
80 })?
81 };
82
83 for fixture in fixtures {
84 let identifier = fixture.identifier.clone();
85 self.fixtures.insert(identifier, fixture);
86 }
87
88 Ok(())
89 }
90
91 pub fn add_fixture(&mut self, fixture: TcpFixture) {
93 let identifier = fixture.identifier.clone();
94 self.fixtures.insert(identifier, fixture);
95 }
96
97 pub fn get_fixture(&self, identifier: &str) -> Option<&TcpFixture> {
99 self.fixtures.get(identifier)
100 }
101
102 pub fn find_matching_fixture(&self, data: &[u8]) -> Option<&TcpFixture> {
104 self.fixtures.values().find(|&fixture| fixture.matches(data)).map(|v| v as _)
106 }
107
108 pub fn get_all_fixtures(&self) -> Vec<&TcpFixture> {
110 self.fixtures.values().collect()
111 }
112
113 pub fn remove_fixture(&mut self, identifier: &str) -> Option<TcpFixture> {
115 self.fixtures.remove(identifier)
116 }
117
118 pub fn clear(&mut self) {
120 self.fixtures.clear();
121 }
122}
123
124impl Default for TcpSpecRegistry {
125 fn default() -> Self {
126 Self::new()
127 }
128}
129
130impl TcpFixture {
131 pub fn matches(&self, data: &[u8]) -> bool {
133 let criteria = &self.match_criteria;
134
135 if let Some(min_len) = criteria.min_length {
137 if data.len() < min_len {
138 return false;
139 }
140 }
141
142 if let Some(max_len) = criteria.max_length {
143 if data.len() > max_len {
144 return false;
145 }
146 }
147
148 if criteria.match_all {
150 return true;
151 }
152
153 if let Some(ref exact_bytes_b64) = criteria.exact_bytes {
155 if let Ok(expected) = base64::decode(exact_bytes_b64) {
156 return data == expected.as_slice();
157 }
158 }
159
160 if let Some(ref hex_pattern) = criteria.data_pattern {
162 if let Ok(expected) = hex::decode(hex_pattern) {
163 return data == expected.as_slice();
164 }
165 }
166
167 if let Some(ref text_pattern) = criteria.text_pattern {
169 if let Ok(re) = regex::Regex::new(text_pattern) {
170 if let Ok(text) = String::from_utf8(data.to_vec()) {
171 return re.is_match(&text);
172 }
173 }
174 }
175
176 false
177 }
178}