1use crate::server::MockServer;
4use crate::{Error, Result};
5use mockforge_core::{Config, FailureConfig, LatencyProfile, ProxyConfig, ServerConfig};
6use std::net::TcpListener;
7use std::path::PathBuf;
8
9pub struct MockServerBuilder {
11 port: Option<u16>,
12 host: Option<String>,
13 config_file: Option<PathBuf>,
14 openapi_spec: Option<PathBuf>,
15 latency_profile: Option<LatencyProfile>,
16 failure_config: Option<FailureConfig>,
17 proxy_config: Option<ProxyConfig>,
18 enable_admin: bool,
19 admin_port: Option<u16>,
20 auto_port: bool,
21 port_range: Option<(u16, u16)>,
22}
23
24impl Default for MockServerBuilder {
25 fn default() -> Self {
26 Self::new()
27 }
28}
29
30impl MockServerBuilder {
31 pub fn new() -> Self {
33 Self {
34 port: None,
35 host: None,
36 config_file: None,
37 openapi_spec: None,
38 latency_profile: None,
39 failure_config: None,
40 proxy_config: None,
41 enable_admin: false,
42 admin_port: None,
43 auto_port: false,
44 port_range: None,
45 }
46 }
47
48 pub fn port(mut self, port: u16) -> Self {
50 self.port = Some(port);
51 self.auto_port = false;
52 self
53 }
54
55 pub fn auto_port(mut self) -> Self {
57 self.auto_port = true;
58 self.port = None;
59 self
60 }
61
62 pub fn port_range(mut self, start: u16, end: u16) -> Self {
65 self.port_range = Some((start, end));
66 self
67 }
68
69 pub fn host(mut self, host: impl Into<String>) -> Self {
71 self.host = Some(host.into());
72 self
73 }
74
75 pub fn config_file(mut self, path: impl Into<PathBuf>) -> Self {
77 self.config_file = Some(path.into());
78 self
79 }
80
81 pub fn openapi_spec(mut self, path: impl Into<PathBuf>) -> Self {
83 self.openapi_spec = Some(path.into());
84 self
85 }
86
87 pub fn latency(mut self, profile: LatencyProfile) -> Self {
89 self.latency_profile = Some(profile);
90 self
91 }
92
93 pub fn failures(mut self, config: FailureConfig) -> Self {
95 self.failure_config = Some(config);
96 self
97 }
98
99 pub fn proxy(mut self, config: ProxyConfig) -> Self {
101 self.proxy_config = Some(config);
102 self
103 }
104
105 pub fn admin(mut self, enabled: bool) -> Self {
107 self.enable_admin = enabled;
108 self
109 }
110
111 pub fn admin_port(mut self, port: u16) -> Self {
113 self.admin_port = Some(port);
114 self
115 }
116
117 pub async fn start(self) -> Result<MockServer> {
119 let mut config = if let Some(config_file) = self.config_file {
121 mockforge_core::load_config(&config_file)
122 .await
123 .map_err(|e| Error::InvalidConfig(e.to_string()))?
124 } else {
125 ServerConfig::default()
126 };
127
128 if self.auto_port {
130 let (start, end) = self.port_range.unwrap_or((30000, 30100));
132 let port = find_available_port(start, end)?;
133 config.http.port = port;
134 } else if let Some(port) = self.port {
135 config.http.port = port;
136 }
137
138 if let Some(host) = self.host {
140 config.http.host = host;
141 }
142 if let Some(spec_path) = self.openapi_spec {
143 config.http.openapi_spec = Some(spec_path.to_string_lossy().to_string());
144 }
145
146 let mut core_config = Config::default();
148
149 if let Some(latency) = self.latency_profile {
150 core_config.latency_enabled = true;
151 core_config.default_latency = latency;
152 }
153
154 if let Some(failures) = self.failure_config {
155 core_config.failures_enabled = true;
156 core_config.failure_config = Some(failures);
157 }
158
159 if let Some(proxy) = self.proxy_config {
160 core_config.proxy = Some(proxy);
161 }
162
163 MockServer::from_config(config, core_config).await
165 }
166}
167
168fn is_port_available(port: u16) -> bool {
175 TcpListener::bind(("127.0.0.1", port)).is_ok()
176}
177
178fn find_available_port(start: u16, end: u16) -> Result<u16> {
191 if start >= end {
193 return Err(Error::InvalidConfig(format!(
194 "Invalid port range: start ({}) must be less than end ({})",
195 start, end
196 )));
197 }
198
199 for port in start..=end {
201 if is_port_available(port) {
202 return Ok(port);
203 }
204 }
205
206 Err(Error::PortDiscoveryFailed(format!(
207 "No available ports found in range {}-{}",
208 start, end
209 )))
210}