mockforge_tcp/
spec_registry.rs1use crate::fixtures::TcpFixture;
4use mockforge_core::Result;
5use std::collections::HashMap;
6use std::path::{Path, PathBuf};
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 for fixture in self.fixtures.values() {
106 if fixture.matches(data) {
107 return Some(fixture);
108 }
109 }
110
111 None
112 }
113
114 pub fn get_all_fixtures(&self) -> Vec<&TcpFixture> {
116 self.fixtures.values().collect()
117 }
118
119 pub fn remove_fixture(&mut self, identifier: &str) -> Option<TcpFixture> {
121 self.fixtures.remove(identifier)
122 }
123
124 pub fn clear(&mut self) {
126 self.fixtures.clear();
127 }
128}
129
130impl Default for TcpSpecRegistry {
131 fn default() -> Self {
132 Self::new()
133 }
134}
135
136impl TcpFixture {
137 pub fn matches(&self, data: &[u8]) -> bool {
139 let criteria = &self.match_criteria;
140
141 if let Some(min_len) = criteria.min_length {
143 if data.len() < min_len {
144 return false;
145 }
146 }
147
148 if let Some(max_len) = criteria.max_length {
149 if data.len() > max_len {
150 return false;
151 }
152 }
153
154 if criteria.match_all {
156 return true;
157 }
158
159 if let Some(ref exact_bytes_b64) = criteria.exact_bytes {
161 if let Ok(expected) = base64::decode(exact_bytes_b64) {
162 return data == expected.as_slice();
163 }
164 }
165
166 if let Some(ref hex_pattern) = criteria.data_pattern {
168 if let Ok(expected) = hex::decode(hex_pattern) {
169 return data == expected.as_slice();
170 }
171 }
172
173 if let Some(ref text_pattern) = criteria.text_pattern {
175 if let Ok(re) = regex::Regex::new(text_pattern) {
176 if let Ok(text) = String::from_utf8(data.to_vec()) {
177 return re.is_match(&text);
178 }
179 }
180 }
181
182 false
183 }
184}