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 #[must_use]
33 pub const fn new() -> Self {
34 Self {
35 port: None,
36 host: None,
37 config_file: None,
38 openapi_spec: None,
39 latency_profile: None,
40 failure_config: None,
41 proxy_config: None,
42 enable_admin: false,
43 admin_port: None,
44 auto_port: false,
45 port_range: None,
46 }
47 }
48
49 #[must_use]
51 pub const fn port(mut self, port: u16) -> Self {
52 self.port = Some(port);
53 self.auto_port = false;
54 self
55 }
56
57 #[must_use]
59 pub const fn auto_port(mut self) -> Self {
60 self.auto_port = true;
61 self.port = None;
62 self
63 }
64
65 #[must_use]
68 pub const fn port_range(mut self, start: u16, end: u16) -> Self {
69 self.port_range = Some((start, end));
70 self
71 }
72
73 pub fn host(mut self, host: impl Into<String>) -> Self {
75 self.host = Some(host.into());
76 self
77 }
78
79 pub fn config_file(mut self, path: impl Into<PathBuf>) -> Self {
81 self.config_file = Some(path.into());
82 self
83 }
84
85 pub fn openapi_spec(mut self, path: impl Into<PathBuf>) -> Self {
87 self.openapi_spec = Some(path.into());
88 self
89 }
90
91 #[must_use]
93 pub fn latency(mut self, profile: LatencyProfile) -> Self {
94 self.latency_profile = Some(profile);
95 self
96 }
97
98 #[must_use]
100 pub fn failures(mut self, config: FailureConfig) -> Self {
101 self.failure_config = Some(config);
102 self
103 }
104
105 #[must_use]
107 pub fn proxy(mut self, config: ProxyConfig) -> Self {
108 self.proxy_config = Some(config);
109 self
110 }
111
112 #[must_use]
114 pub const fn admin(mut self, enabled: bool) -> Self {
115 self.enable_admin = enabled;
116 self
117 }
118
119 #[must_use]
121 pub const fn admin_port(mut self, port: u16) -> Self {
122 self.admin_port = Some(port);
123 self
124 }
125
126 pub async fn start(self) -> Result<MockServer> {
128 let mut config = if let Some(config_file) = self.config_file {
130 mockforge_core::load_config(&config_file)
131 .await
132 .map_err(|e| Error::InvalidConfig(e.to_string()))?
133 } else {
134 ServerConfig::default()
135 };
136
137 if self.auto_port {
139 let (start, end) = self.port_range.unwrap_or((30000, 30100));
141 let port = find_available_port(start, end)?;
142 config.http.port = port;
143 } else if let Some(port) = self.port {
144 config.http.port = port;
145 }
146
147 if let Some(host) = self.host {
149 config.http.host = host;
150 }
151 if let Some(spec_path) = self.openapi_spec {
152 config.http.openapi_spec = Some(spec_path.to_string_lossy().to_string());
153 }
154
155 let mut core_config = Config::default();
157
158 if let Some(latency) = self.latency_profile {
159 core_config.latency_enabled = true;
160 core_config.default_latency = latency;
161 }
162
163 if let Some(failures) = self.failure_config {
164 core_config.failures_enabled = true;
165 core_config.failure_config = Some(failures);
166 }
167
168 if let Some(proxy) = self.proxy_config {
169 core_config.proxy = Some(proxy);
170 }
171
172 MockServer::from_config(config, core_config).await
174 }
175}
176
177fn is_port_available(port: u16) -> bool {
184 TcpListener::bind(("127.0.0.1", port)).is_ok()
185}
186
187fn find_available_port(start: u16, end: u16) -> Result<u16> {
200 if start >= end {
202 return Err(Error::InvalidConfig(format!(
203 "Invalid port range: start ({start}) must be less than end ({end})"
204 )));
205 }
206
207 for port in start..=end {
209 if is_port_available(port) {
210 return Ok(port);
211 }
212 }
213
214 Err(Error::PortDiscoveryFailed(format!(
215 "No available ports found in range {start}-{end}"
216 )))
217}