1pub mod http_bridge;
8pub mod proto_parser;
9pub mod router;
10pub mod service_generator;
11
12use crate::reflection::{MockReflectionProxy, ProxyConfig};
13use proto_parser::ProtoParser;
14use service_generator::DynamicGrpcService;
15use std::collections::HashMap;
16use std::sync::Arc;
17use tonic::transport::Server;
18use tonic_reflection::server::Builder as ReflectionBuilder;
19use tracing::*;
20
21#[derive(Debug, Clone)]
23pub struct GrpcTlsConfig {
24 pub cert_path: String,
26 pub key_path: String,
28 pub client_ca_path: Option<String>,
30}
31
32impl GrpcTlsConfig {
33 pub fn new(cert_path: impl Into<String>, key_path: impl Into<String>) -> Self {
35 Self {
36 cert_path: cert_path.into(),
37 key_path: key_path.into(),
38 client_ca_path: None,
39 }
40 }
41
42 pub fn with_mtls(
44 cert_path: impl Into<String>,
45 key_path: impl Into<String>,
46 client_ca_path: impl Into<String>,
47 ) -> Self {
48 Self {
49 cert_path: cert_path.into(),
50 key_path: key_path.into(),
51 client_ca_path: Some(client_ca_path.into()),
52 }
53 }
54
55 pub fn from_env() -> Option<Self> {
62 let cert_path = std::env::var("GRPC_TLS_CERT").ok()?;
63 let key_path = std::env::var("GRPC_TLS_KEY").ok()?;
64 let client_ca_path = std::env::var("GRPC_TLS_CLIENT_CA").ok();
65
66 Some(Self {
67 cert_path,
68 key_path,
69 client_ca_path,
70 })
71 }
72}
73
74#[derive(Debug, Clone)]
76pub struct DynamicGrpcConfig {
77 pub proto_dir: String,
79 pub enable_reflection: bool,
81 pub excluded_services: Vec<String>,
83 pub http_bridge: Option<http_bridge::HttpBridgeConfig>,
85 pub tls: Option<GrpcTlsConfig>,
87 pub overrides: Vec<mockforge_core::config::GrpcOverride>,
90}
91
92impl Default for DynamicGrpcConfig {
93 fn default() -> Self {
94 Self {
95 proto_dir: "proto".to_string(),
96 enable_reflection: false,
97 excluded_services: Vec::new(),
98 overrides: Vec::new(),
99 http_bridge: Some(http_bridge::HttpBridgeConfig {
100 enabled: true,
101 ..Default::default()
102 }),
103 tls: GrpcTlsConfig::from_env(),
105 }
106 }
107}
108
109#[derive(Clone)]
111pub struct ServiceRegistry {
112 services: HashMap<String, Arc<DynamicGrpcService>>,
114 descriptor_pool: prost_reflect::DescriptorPool,
116 overrides: Arc<Vec<mockforge_core::config::GrpcOverride>>,
119}
120
121impl Default for ServiceRegistry {
122 fn default() -> Self {
123 Self::new()
124 }
125}
126
127impl ServiceRegistry {
128 pub fn descriptor_pool(&self) -> &prost_reflect::DescriptorPool {
130 &self.descriptor_pool
131 }
132
133 pub fn new() -> Self {
135 Self {
136 services: HashMap::new(),
137 descriptor_pool: prost_reflect::DescriptorPool::new(),
138 overrides: Arc::new(Vec::new()),
139 }
140 }
141
142 pub fn with_descriptor_pool(descriptor_pool: prost_reflect::DescriptorPool) -> Self {
144 Self {
145 services: HashMap::new(),
146 descriptor_pool,
147 overrides: Arc::new(Vec::new()),
148 }
149 }
150
151 pub fn set_overrides(&mut self, overrides: Vec<mockforge_core::config::GrpcOverride>) {
154 self.overrides = Arc::new(overrides);
155 }
156
157 pub fn overrides(&self) -> &[mockforge_core::config::GrpcOverride] {
159 self.overrides.as_ref()
160 }
161
162 pub fn set_descriptor_pool(&mut self, pool: prost_reflect::DescriptorPool) {
164 self.descriptor_pool = pool;
165 }
166
167 pub fn register(&mut self, name: String, service: DynamicGrpcService) {
169 self.services.insert(name, Arc::new(service));
170 }
171
172 pub fn get(&self, name: &str) -> Option<&Arc<DynamicGrpcService>> {
174 self.services.get(name)
175 }
176
177 pub fn service_names(&self) -> Vec<String> {
179 self.services.keys().cloned().collect()
180 }
181}
182
183pub async fn discover_services(
185 config: &DynamicGrpcConfig,
186) -> Result<ServiceRegistry, Box<dyn std::error::Error + Send + Sync>> {
187 use std::time::Instant;
188
189 let discovery_start = Instant::now();
190 info!("Discovering gRPC services from proto directory: {}", config.proto_dir);
191
192 let parse_start = Instant::now();
194 let mut parser = ProtoParser::new();
195 parser.parse_directory(&config.proto_dir).await?;
196 let parse_duration = parse_start.elapsed();
197 info!("Proto file parsing completed (took {:?})", parse_duration);
198
199 let registry_start = Instant::now();
201 let mut registry = ServiceRegistry::new();
202 let services = parser.services().clone();
204 let descriptor_pool = parser.into_pool();
205
206 registry.set_descriptor_pool(descriptor_pool);
207 if !config.overrides.is_empty() {
208 info!("Loaded {} gRPC method override rule(s) from config", config.overrides.len());
209 registry.set_overrides(config.overrides.clone());
210 }
211 let registry_duration = registry_start.elapsed();
212 debug!("Registry creation completed (took {:?})", registry_duration);
213
214 let service_reg_start = Instant::now();
216 for (service_name, proto_service) in services {
217 if config.excluded_services.contains(&service_name) {
219 info!("Skipping excluded service: {}", service_name);
220 continue;
221 }
222
223 let dynamic_service = DynamicGrpcService::new(proto_service.clone(), None);
225 registry.register(service_name.clone(), dynamic_service);
226
227 debug!("Registered service: {}", service_name);
228 }
229 let service_reg_duration = service_reg_start.elapsed();
230 info!(
231 "Service registration completed for {} services (took {:?})",
232 registry.service_names().len(),
233 service_reg_duration
234 );
235
236 let total_discovery_duration = discovery_start.elapsed();
237 info!("Service discovery completed (total time: {:?})", total_discovery_duration);
238 Ok(registry)
239}
240
241pub async fn start_dynamic_server(
243 port: u16,
244 config: DynamicGrpcConfig,
245 latency_profile: Option<mockforge_foundation::latency::LatencyProfile>,
246) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
247 let listener =
248 tokio::net::TcpListener::bind(mockforge_core::wildcard_socket_addr(port)).await?;
249 start_dynamic_server_with_listener(listener, config, latency_profile).await
250}
251
252pub async fn start_dynamic_server_with_listener(
257 listener: tokio::net::TcpListener,
258 config: DynamicGrpcConfig,
259 latency_profile: Option<mockforge_foundation::latency::LatencyProfile>,
260) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
261 use std::time::Instant;
262
263 let startup_start = Instant::now();
264
265 #[cfg(feature = "data-faker")]
266 mockforge_data::provider::register_core_faker_provider();
267
268 let _latency_injector = latency_profile.map(|profile| {
269 mockforge_foundation::latency::LatencyInjector::new(profile, Default::default())
270 });
271
272 let registry = discover_services(&config).await?;
274 let registry_arc = Arc::new(registry);
275
276 let addr = listener.local_addr()?;
278 info!(
279 "Dynamic server listening on {} with {} services",
280 addr,
281 registry_arc.service_names().len()
282 );
283
284 let proxy_config = ProxyConfig::default();
286
287 let reflection_start = Instant::now();
289 let mock_proxy = MockReflectionProxy::new(proxy_config, registry_arc.clone()).await?;
290 let reflection_duration = reflection_start.elapsed();
291 info!("gRPC reflection proxy created (took {:?})", reflection_duration);
292
293 let total_startup_duration = startup_start.elapsed();
294 info!("gRPC server startup completed (total time: {:?})", total_startup_duration);
295
296 start_grpc_only_server(listener, &config, registry_arc.clone(), mock_proxy).await?;
300
301 Ok(())
304}
305
306pub async fn start_dynamic_grpc_server(
308 port: u16,
309 config: DynamicGrpcConfig,
310 latency_profile: Option<mockforge_foundation::latency::LatencyProfile>,
311) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
312 let mut grpc_only_config = config;
314 grpc_only_config.http_bridge = None;
315
316 start_dynamic_server(port, grpc_only_config, latency_profile).await
317}
318
319async fn start_grpc_only_server(
321 listener: tokio::net::TcpListener,
322 config: &DynamicGrpcConfig,
323 registry_arc: Arc<ServiceRegistry>,
324 _mock_proxy: MockReflectionProxy,
325) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
326 use tonic::transport::{Certificate, Identity, ServerTlsConfig};
327
328 let mut server_builder = if let Some(tls_config) = &config.tls {
330 info!("Configuring gRPC server with TLS");
331
332 let cert = tokio::fs::read(&tls_config.cert_path).await.map_err(|e| {
334 error!("Failed to read TLS certificate from {}: {}", tls_config.cert_path, e);
335 Box::<dyn std::error::Error + Send + Sync>::from(format!(
336 "Failed to read TLS certificate: {}",
337 e
338 ))
339 })?;
340
341 let key = tokio::fs::read(&tls_config.key_path).await.map_err(|e| {
342 error!("Failed to read TLS key from {}: {}", tls_config.key_path, e);
343 Box::<dyn std::error::Error + Send + Sync>::from(format!(
344 "Failed to read TLS key: {}",
345 e
346 ))
347 })?;
348
349 let identity = Identity::from_pem(cert, key);
350
351 let mut tls = ServerTlsConfig::new().identity(identity);
352
353 if let Some(client_ca_path) = &tls_config.client_ca_path {
355 info!("Configuring mutual TLS (mTLS) with client certificate verification");
356 let client_ca = tokio::fs::read(client_ca_path).await.map_err(|e| {
357 error!("Failed to read client CA from {}: {}", client_ca_path, e);
358 Box::<dyn std::error::Error + Send + Sync>::from(format!(
359 "Failed to read client CA: {}",
360 e
361 ))
362 })?;
363 tls = tls.client_ca_root(Certificate::from_pem(client_ca));
364 }
365
366 Server::builder().tls_config(tls).map_err(|e| {
367 error!("Failed to configure TLS: {}", e);
368 Box::<dyn std::error::Error + Send + Sync>::from(format!(
369 "Failed to configure TLS: {}",
370 e
371 ))
372 })?
373 } else {
374 info!("gRPC server running in plaintext mode (no TLS configured)");
375 Server::builder()
376 };
377
378 use std::net::SocketAddr;
379
380 let grpc_addr: SocketAddr = listener.local_addr()?;
381
382 info!(
384 "Starting gRPC server on {} with {} discovered services",
385 grpc_addr,
386 registry_arc.service_names().len()
387 );
388 for service_name in registry_arc.service_names() {
389 info!(" - Dynamic service: {}", service_name);
390 }
391
392 use crate::generated::greeter_server::{Greeter, GreeterServer};
394 use crate::generated::{HelloReply, HelloRequest};
395 use tonic::{Request, Response, Status};
396
397 #[derive(Debug, Default)]
398 struct MockGreeterService;
399
400 use futures::StreamExt;
401 use std::pin::Pin;
402 use tokio_stream::wrappers::ReceiverStream;
403
404 #[tonic::async_trait]
405 impl Greeter for MockGreeterService {
406 type SayHelloStreamStream =
407 Pin<Box<dyn futures::Stream<Item = Result<HelloReply, Status>> + Send>>;
408 type ChatStream = Pin<Box<dyn futures::Stream<Item = Result<HelloReply, Status>> + Send>>;
409
410 async fn say_hello(
411 &self,
412 request: Request<HelloRequest>,
413 ) -> Result<Response<HelloReply>, Status> {
414 info!("gRPC say_hello request: {:?}", request);
415 let req = request.into_inner();
416 let reply = HelloReply {
417 message: format!("Hello {}! This is a mock response from MockForge", req.name),
418 metadata: None,
419 items: vec![],
420 };
421 Ok(Response::new(reply))
422 }
423
424 async fn say_hello_stream(
425 &self,
426 request: Request<HelloRequest>,
427 ) -> Result<Response<Self::SayHelloStreamStream>, Status> {
428 info!("gRPC say_hello_stream request: {:?}", request);
429 let name = request.into_inner().name;
430 let (tx, rx) = tokio::sync::mpsc::channel(128);
431 tokio::spawn(async move {
432 for i in 1..=5 {
433 let reply = HelloReply {
434 message: format!(
435 "Hello {}! Stream message {} of 5 from MockForge",
436 name, i
437 ),
438 metadata: None,
439 items: vec![],
440 };
441 if tx.send(Ok(reply)).await.is_err() {
442 break;
443 }
444 tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
445 }
446 });
447 let stream = ReceiverStream::new(rx);
448 Ok(Response::new(Box::pin(stream) as Self::SayHelloStreamStream))
449 }
450
451 async fn say_hello_client_stream(
452 &self,
453 request: Request<tonic::Streaming<HelloRequest>>,
454 ) -> Result<Response<HelloReply>, Status> {
455 info!("gRPC say_hello_client_stream started");
456 let mut stream = request.into_inner();
457 let mut names = Vec::new();
458 let mut count = 0;
459 while let Some(req) = stream.next().await {
460 match req {
461 Ok(hello_request) => {
462 names.push(hello_request.name);
463 count += 1;
464 }
465 Err(e) => {
466 error!("Error receiving client stream message: {}", e);
467 return Err(Status::internal(format!("Stream error: {}", e)));
468 }
469 }
470 }
471 let message = if names.is_empty() {
472 "Hello! No names received in the stream.".to_string()
473 } else {
474 format!(
475 "Hello {}! Received {} messages from MockForge client stream.",
476 names.join(", "),
477 count
478 )
479 };
480 Ok(Response::new(HelloReply {
481 message,
482 metadata: None,
483 items: vec![],
484 }))
485 }
486
487 async fn chat(
488 &self,
489 request: Request<tonic::Streaming<HelloRequest>>,
490 ) -> Result<Response<Self::ChatStream>, Status> {
491 info!("gRPC chat (bidirectional streaming) started");
492 let mut stream = request.into_inner();
493 let (tx, rx) = tokio::sync::mpsc::channel(128);
494 tokio::spawn(async move {
495 let mut message_count = 0;
496 while let Some(req) = stream.next().await {
497 match req {
498 Ok(hello_request) => {
499 message_count += 1;
500 let reply = HelloReply {
501 message: format!(
502 "Chat response {}: Hello {}! from MockForge",
503 message_count, hello_request.name
504 ),
505 metadata: None,
506 items: vec![],
507 };
508 if tx.send(Ok(reply)).await.is_err() {
509 break;
510 }
511 }
512 Err(e) => {
513 error!("Chat stream error: {}", e);
514 let _ = tx
515 .send(Err(Status::internal(format!("Stream error: {}", e))))
516 .await;
517 break;
518 }
519 }
520 }
521 info!("Chat session ended after {} messages", message_count);
522 });
523 let output_stream = ReceiverStream::new(rx);
524 Ok(Response::new(Box::pin(output_stream) as Self::ChatStream))
525 }
526 }
527
528 let mut routes_builder = tonic::service::RoutesBuilder::default();
530 routes_builder.add_service(GreeterServer::new(MockGreeterService));
531 info!("Registered built-in Greeter service");
532
533 if config.enable_reflection {
535 let encoded_fd_set = registry_arc.descriptor_pool().encode_to_vec();
536 let reflection_service = ReflectionBuilder::configure()
537 .register_encoded_file_descriptor_set(&encoded_fd_set)
538 .build_v1()
539 .map_err(|e| {
540 error!("Failed to build reflection service: {}", e);
541 Box::<dyn std::error::Error + Send + Sync>::from(format!(
542 "Failed to build reflection service: {}",
543 e
544 ))
545 })?;
546 routes_builder.add_service(reflection_service);
547 info!("gRPC reflection service enabled");
548 }
549
550 let registry_for_fallback = registry_arc.clone();
552 let axum_router =
553 routes_builder
554 .routes()
555 .into_axum_router()
556 .fallback(move |req: axum::extract::Request| {
557 let registry = registry_for_fallback.clone();
558 async move {
559 let path = req.uri().path().to_string();
560 match router::parse_grpc_path(&path) {
561 Some((service_name, method_name)) => {
562 let body = axum::body::to_bytes(req.into_body(), 4 * 1024 * 1024)
564 .await
565 .unwrap_or_default();
566
567 match router::handle_dynamic_grpc_request(
568 ®istry,
569 service_name,
570 method_name,
571 body,
572 )
573 .await
574 {
575 Ok(response) => response,
576 Err(status) => router::create_grpc_error_response(status),
577 }
578 }
579 None => router::create_grpc_error_response(Status::unimplemented(
580 "Unknown path",
581 )),
582 }
583 }
584 });
585
586 let routes = tonic::service::Routes::from(axum_router);
588 server_builder
589 .add_routes(routes)
590 .serve_with_incoming(tokio_stream::wrappers::TcpListenerStream::new(listener))
591 .await?;
592
593 info!("gRPC server stopped");
594 Ok(())
595}
596
597#[cfg(test)]
600mod tests {
601 #[test]
602 fn test_module_compiles() {
603 }
605}