1pub mod http_bridge;
8pub mod proto_parser;
9pub mod service_generator;
10
11use crate::reflection::{MockReflectionProxy, ProxyConfig};
12use proto_parser::ProtoParser;
13use service_generator::DynamicGrpcService;
14use std::collections::HashMap;
15use std::sync::Arc;
16use tonic::transport::Server;
17use tonic_reflection::server::Builder as ReflectionBuilder;
18use tracing::*;
19
20#[derive(Debug, Clone)]
22pub struct GrpcTlsConfig {
23 pub cert_path: String,
25 pub key_path: String,
27 pub client_ca_path: Option<String>,
29}
30
31impl GrpcTlsConfig {
32 pub fn new(cert_path: impl Into<String>, key_path: impl Into<String>) -> Self {
34 Self {
35 cert_path: cert_path.into(),
36 key_path: key_path.into(),
37 client_ca_path: None,
38 }
39 }
40
41 pub fn with_mtls(
43 cert_path: impl Into<String>,
44 key_path: impl Into<String>,
45 client_ca_path: impl Into<String>,
46 ) -> Self {
47 Self {
48 cert_path: cert_path.into(),
49 key_path: key_path.into(),
50 client_ca_path: Some(client_ca_path.into()),
51 }
52 }
53
54 pub fn from_env() -> Option<Self> {
61 let cert_path = std::env::var("GRPC_TLS_CERT").ok()?;
62 let key_path = std::env::var("GRPC_TLS_KEY").ok()?;
63 let client_ca_path = std::env::var("GRPC_TLS_CLIENT_CA").ok();
64
65 Some(Self {
66 cert_path,
67 key_path,
68 client_ca_path,
69 })
70 }
71}
72
73#[derive(Debug, Clone)]
75pub struct DynamicGrpcConfig {
76 pub proto_dir: String,
78 pub enable_reflection: bool,
80 pub excluded_services: Vec<String>,
82 pub http_bridge: Option<http_bridge::HttpBridgeConfig>,
84 pub tls: Option<GrpcTlsConfig>,
86}
87
88impl Default for DynamicGrpcConfig {
89 fn default() -> Self {
90 Self {
91 proto_dir: "proto".to_string(),
92 enable_reflection: false,
93 excluded_services: Vec::new(),
94 http_bridge: Some(http_bridge::HttpBridgeConfig {
95 enabled: true,
96 ..Default::default()
97 }),
98 tls: GrpcTlsConfig::from_env(),
100 }
101 }
102}
103
104#[derive(Clone)]
106pub struct ServiceRegistry {
107 services: HashMap<String, Arc<DynamicGrpcService>>,
109 descriptor_pool: prost_reflect::DescriptorPool,
111}
112
113impl Default for ServiceRegistry {
114 fn default() -> Self {
115 Self::new()
116 }
117}
118
119impl ServiceRegistry {
120 pub fn descriptor_pool(&self) -> &prost_reflect::DescriptorPool {
122 &self.descriptor_pool
123 }
124
125 pub fn new() -> Self {
127 Self {
128 services: HashMap::new(),
129 descriptor_pool: prost_reflect::DescriptorPool::new(),
130 }
131 }
132
133 pub fn with_descriptor_pool(descriptor_pool: prost_reflect::DescriptorPool) -> Self {
135 Self {
136 services: HashMap::new(),
137 descriptor_pool,
138 }
139 }
140
141 pub fn set_descriptor_pool(&mut self, pool: prost_reflect::DescriptorPool) {
143 self.descriptor_pool = pool;
144 }
145
146 pub fn register(&mut self, name: String, service: DynamicGrpcService) {
148 self.services.insert(name, Arc::new(service));
149 }
150
151 pub fn get(&self, name: &str) -> Option<&Arc<DynamicGrpcService>> {
153 self.services.get(name)
154 }
155
156 pub fn service_names(&self) -> Vec<String> {
158 self.services.keys().cloned().collect()
159 }
160}
161
162pub async fn discover_services(
164 config: &DynamicGrpcConfig,
165) -> Result<ServiceRegistry, Box<dyn std::error::Error + Send + Sync>> {
166 use std::time::Instant;
167
168 let discovery_start = Instant::now();
169 info!("Discovering gRPC services from proto directory: {}", config.proto_dir);
170
171 let parse_start = Instant::now();
173 let mut parser = ProtoParser::new();
174 parser.parse_directory(&config.proto_dir).await?;
175 let parse_duration = parse_start.elapsed();
176 info!("Proto file parsing completed (took {:?})", parse_duration);
177
178 let registry_start = Instant::now();
180 let mut registry = ServiceRegistry::new();
181 let services = parser.services().clone();
183 let descriptor_pool = parser.into_pool();
184
185 registry.set_descriptor_pool(descriptor_pool);
186 let registry_duration = registry_start.elapsed();
187 debug!("Registry creation completed (took {:?})", registry_duration);
188
189 let service_reg_start = Instant::now();
191 for (service_name, proto_service) in services {
192 if config.excluded_services.contains(&service_name) {
194 info!("Skipping excluded service: {}", service_name);
195 continue;
196 }
197
198 let dynamic_service = DynamicGrpcService::new(proto_service.clone(), None);
200 registry.register(service_name.clone(), dynamic_service);
201
202 debug!("Registered service: {}", service_name);
203 }
204 let service_reg_duration = service_reg_start.elapsed();
205 info!(
206 "Service registration completed for {} services (took {:?})",
207 registry.service_names().len(),
208 service_reg_duration
209 );
210
211 let total_discovery_duration = discovery_start.elapsed();
212 info!("Service discovery completed (total time: {:?})", total_discovery_duration);
213 Ok(registry)
214}
215
216pub async fn start_dynamic_server(
218 port: u16,
219 config: DynamicGrpcConfig,
220 latency_profile: Option<mockforge_core::LatencyProfile>,
221) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
222 use std::time::Instant;
223
224 let startup_start = Instant::now();
225
226 #[cfg(feature = "data-faker")]
227 mockforge_data::provider::register_core_faker_provider();
228
229 let _latency_injector = latency_profile
230 .map(|profile| mockforge_core::latency::LatencyInjector::new(profile, Default::default()));
231
232 let registry = discover_services(&config).await?;
234 let registry_arc = Arc::new(registry);
235
236 let addr = mockforge_core::wildcard_socket_addr(port);
238 info!(
239 "Dynamic server listening on {} with {} services",
240 addr,
241 registry_arc.service_names().len()
242 );
243
244 let proxy_config = ProxyConfig::default();
246
247 let reflection_start = Instant::now();
249 let mock_proxy = MockReflectionProxy::new(proxy_config, registry_arc.clone()).await?;
250 let reflection_duration = reflection_start.elapsed();
251 info!("gRPC reflection proxy created (took {:?})", reflection_duration);
252
253 let total_startup_duration = startup_start.elapsed();
254 info!("gRPC server startup completed (total time: {:?})", total_startup_duration);
255
256 start_grpc_only_server(port, &config, registry_arc.clone(), mock_proxy).await?;
260
261 Ok(())
264}
265
266pub async fn start_dynamic_grpc_server(
268 port: u16,
269 config: DynamicGrpcConfig,
270 latency_profile: Option<mockforge_core::LatencyProfile>,
271) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
272 let mut grpc_only_config = config;
274 grpc_only_config.http_bridge = None;
275
276 start_dynamic_server(port, grpc_only_config, latency_profile).await
277}
278
279async fn start_grpc_only_server(
281 port: u16,
282 config: &DynamicGrpcConfig,
283 registry_arc: Arc<ServiceRegistry>,
284 _mock_proxy: MockReflectionProxy,
285) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
286 use tonic::transport::{Certificate, Identity, ServerTlsConfig};
287
288 let mut server_builder = if let Some(tls_config) = &config.tls {
290 info!("Configuring gRPC server with TLS");
291
292 let cert = tokio::fs::read(&tls_config.cert_path).await.map_err(|e| {
294 error!("Failed to read TLS certificate from {}: {}", tls_config.cert_path, e);
295 Box::<dyn std::error::Error + Send + Sync>::from(format!(
296 "Failed to read TLS certificate: {}",
297 e
298 ))
299 })?;
300
301 let key = tokio::fs::read(&tls_config.key_path).await.map_err(|e| {
302 error!("Failed to read TLS key from {}: {}", tls_config.key_path, e);
303 Box::<dyn std::error::Error + Send + Sync>::from(format!(
304 "Failed to read TLS key: {}",
305 e
306 ))
307 })?;
308
309 let identity = Identity::from_pem(cert, key);
310
311 let mut tls = ServerTlsConfig::new().identity(identity);
312
313 if let Some(client_ca_path) = &tls_config.client_ca_path {
315 info!("Configuring mutual TLS (mTLS) with client certificate verification");
316 let client_ca = tokio::fs::read(client_ca_path).await.map_err(|e| {
317 error!("Failed to read client CA from {}: {}", client_ca_path, e);
318 Box::<dyn std::error::Error + Send + Sync>::from(format!(
319 "Failed to read client CA: {}",
320 e
321 ))
322 })?;
323 tls = tls.client_ca_root(Certificate::from_pem(client_ca));
324 }
325
326 Server::builder().tls_config(tls).map_err(|e| {
327 error!("Failed to configure TLS: {}", e);
328 Box::<dyn std::error::Error + Send + Sync>::from(format!(
329 "Failed to configure TLS: {}",
330 e
331 ))
332 })?
333 } else {
334 info!("gRPC server running in plaintext mode (no TLS configured)");
335 Server::builder()
336 };
337
338 info!(
340 "Starting gRPC server on {} with {} discovered services",
341 mockforge_core::wildcard_socket_addr(port),
342 registry_arc.service_names().len()
343 );
344
345 for service_name in registry_arc.service_names() {
347 info!(" - Service: {}", service_name);
348 }
349
350 use std::net::SocketAddr;
353
354 let grpc_addr: SocketAddr = mockforge_core::wildcard_socket_addr(port);
355
356 info!("gRPC server listening on {} (basic implementation)", grpc_addr);
357 info!("Discovered services are logged but not yet fully implemented:");
358 for service_name in registry_arc.service_names() {
359 info!(" - {}", service_name);
360 }
361
362 use crate::generated::greeter_server::{Greeter, GreeterServer};
364 use crate::generated::{HelloReply, HelloRequest};
365 use tonic::{Request, Response, Status};
366
367 #[derive(Debug, Default)]
369 pub struct MockGreeterService;
370
371 #[tonic::async_trait]
372 impl Greeter for MockGreeterService {
373 type SayHelloStreamStream = futures::stream::Empty<Result<HelloReply, Status>>;
374 type ChatStream = futures::stream::Empty<Result<HelloReply, Status>>;
375
376 async fn say_hello(
377 &self,
378 request: Request<HelloRequest>,
379 ) -> Result<Response<HelloReply>, Status> {
380 println!("Got a request: {:?}", request);
381
382 let req = request.into_inner();
383 let reply = HelloReply {
384 message: format!("Hello {}! This is a mock response from MockForge", req.name),
385 metadata: None,
386 items: vec![],
387 };
388
389 Ok(Response::new(reply))
390 }
391
392 async fn say_hello_stream(
393 &self,
394 _request: Request<HelloRequest>,
395 ) -> Result<Response<Self::SayHelloStreamStream>, Status> {
396 Err(Status::unimplemented("say_hello_stream not yet implemented"))
397 }
398
399 async fn say_hello_client_stream(
400 &self,
401 _request: Request<tonic::Streaming<HelloRequest>>,
402 ) -> Result<Response<HelloReply>, Status> {
403 Err(Status::unimplemented("say_hello_client_stream not yet implemented"))
404 }
405
406 async fn chat(
407 &self,
408 _request: Request<tonic::Streaming<HelloRequest>>,
409 ) -> Result<Response<Self::ChatStream>, Status> {
410 Err(Status::unimplemented("chat not yet implemented"))
411 }
412 }
413
414 let greeter = MockGreeterService;
415
416 info!("gRPC server listening on {} with Greeter service", grpc_addr);
417
418 let mut router = server_builder.add_service(GreeterServer::new(greeter));
420
421 if config.enable_reflection {
423 let encoded_fd_set = registry_arc.descriptor_pool().encode_to_vec();
425 let reflection_service = ReflectionBuilder::configure()
426 .register_encoded_file_descriptor_set(&encoded_fd_set)
427 .build_v1()
428 .map_err(|e| {
429 error!("Failed to build reflection service: {}", e);
430 Box::<dyn std::error::Error + Send + Sync>::from(format!(
431 "Failed to build reflection service: {}",
432 e
433 ))
434 })?;
435
436 router = router.add_service(reflection_service);
437 info!("gRPC reflection service enabled");
438 }
439
440 router.serve(grpc_addr).await?;
441
442 Ok(())
443}
444
445#[cfg(test)]
448mod tests {
449
450 #[test]
451 fn test_module_compiles() {}
452}