mockforge_grpc/dynamic/
mod.rs

1//! Dynamic gRPC service discovery and registration
2//!
3//! This module provides functionality to dynamically discover and register
4//! gRPC services from proto files, making the gRPC mock system flexible
5//! for different applications and developers.
6
7pub 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/// TLS configuration for gRPC server
21#[derive(Debug, Clone)]
22pub struct GrpcTlsConfig {
23    /// Path to the TLS certificate file (PEM format)
24    pub cert_path: String,
25    /// Path to the TLS private key file (PEM format)
26    pub key_path: String,
27    /// Optional path to CA certificate for client certificate verification (mTLS)
28    pub client_ca_path: Option<String>,
29}
30
31impl GrpcTlsConfig {
32    /// Create a new TLS configuration
33    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    /// Create TLS configuration with mutual TLS (client certificate verification)
42    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    /// Create TLS configuration from environment variables
55    ///
56    /// Uses:
57    /// - GRPC_TLS_CERT: Path to certificate file
58    /// - GRPC_TLS_KEY: Path to private key file
59    /// - GRPC_TLS_CLIENT_CA: Optional path to client CA for mTLS
60    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/// Configuration for dynamic gRPC service discovery
74#[derive(Debug, Clone)]
75pub struct DynamicGrpcConfig {
76    /// Directory containing proto files
77    pub proto_dir: String,
78    /// Whether to enable reflection
79    pub enable_reflection: bool,
80    /// Services to exclude from discovery
81    pub excluded_services: Vec<String>,
82    /// HTTP bridge configuration
83    pub http_bridge: Option<http_bridge::HttpBridgeConfig>,
84    /// TLS configuration (None for plaintext)
85    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            // Check for TLS configuration from environment
99            tls: GrpcTlsConfig::from_env(),
100        }
101    }
102}
103
104/// A registry of discovered gRPC services
105#[derive(Clone)]
106pub struct ServiceRegistry {
107    /// Map of service names to their implementations
108    services: HashMap<String, Arc<DynamicGrpcService>>,
109    /// Descriptor pool containing parsed proto definitions
110    descriptor_pool: prost_reflect::DescriptorPool,
111}
112
113impl Default for ServiceRegistry {
114    fn default() -> Self {
115        Self::new()
116    }
117}
118
119impl ServiceRegistry {
120    /// Get the descriptor pool
121    pub fn descriptor_pool(&self) -> &prost_reflect::DescriptorPool {
122        &self.descriptor_pool
123    }
124
125    /// Create a new service registry
126    pub fn new() -> Self {
127        Self {
128            services: HashMap::new(),
129            descriptor_pool: prost_reflect::DescriptorPool::new(),
130        }
131    }
132
133    /// Create a service registry with a descriptor pool
134    pub fn with_descriptor_pool(descriptor_pool: prost_reflect::DescriptorPool) -> Self {
135        Self {
136            services: HashMap::new(),
137            descriptor_pool,
138        }
139    }
140
141    /// Set the descriptor pool (useful when building registry incrementally)
142    pub fn set_descriptor_pool(&mut self, pool: prost_reflect::DescriptorPool) {
143        self.descriptor_pool = pool;
144    }
145
146    /// Register a service implementation
147    pub fn register(&mut self, name: String, service: DynamicGrpcService) {
148        self.services.insert(name, Arc::new(service));
149    }
150
151    /// Get a service by name
152    pub fn get(&self, name: &str) -> Option<&Arc<DynamicGrpcService>> {
153        self.services.get(name)
154    }
155
156    /// List all registered service names
157    pub fn service_names(&self) -> Vec<String> {
158        self.services.keys().cloned().collect()
159    }
160}
161
162/// Discover and register services from proto files
163pub 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    // Parse proto files
172    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    // Create registry with the descriptor pool from the parser
179    let registry_start = Instant::now();
180    let mut registry = ServiceRegistry::new();
181    // Extract services from parser and move descriptor pool
182    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    // Create dynamic services from parsed proto definitions
190    let service_reg_start = Instant::now();
191    for (service_name, proto_service) in services {
192        // Skip excluded services
193        if config.excluded_services.contains(&service_name) {
194            info!("Skipping excluded service: {}", service_name);
195            continue;
196        }
197
198        // Create dynamic service
199        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
216/// Start a dynamic server with both gRPC and HTTP bridge support
217pub 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    // Discover services
233    let registry = discover_services(&config).await?;
234    let registry_arc = Arc::new(registry);
235
236    // Use shared server utilities for consistent address creation
237    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    // Create proxy configuration
245    let proxy_config = ProxyConfig::default();
246
247    // Create mock reflection proxy
248    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 HTTP server (bridge) if enabled
257    // Currently, just start the gRPC server directly
258    // HTTP bridge functionality is disabled
259    start_grpc_only_server(port, &config, registry_arc.clone(), mock_proxy).await?;
260
261    // HTTP bridge is disabled, no server handle to wait for
262
263    Ok(())
264}
265
266/// Start a gRPC-only server (for backward compatibility)
267pub 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    // Disable HTTP bridge
273    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
279/// Start the gRPC-only server implementation
280async 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    // Create server builder with optional TLS
289    let mut server_builder = if let Some(tls_config) = &config.tls {
290        info!("Configuring gRPC server with TLS");
291
292        // Read certificate and key files
293        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        // Add client CA for mTLS if configured
314        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    // Start actual gRPC server on the specified port
339    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    // Log discovered services
346    for service_name in registry_arc.service_names() {
347        info!("  - Service: {}", service_name);
348    }
349
350    // Create a basic gRPC server that at least starts successfully
351    // Full implementation would require generating actual service implementations
352    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    // Create a basic gRPC server with the discovered services
363    use crate::generated::greeter_server::{Greeter, GreeterServer};
364    use crate::generated::{HelloReply, HelloRequest};
365    use tonic::{Request, Response, Status};
366
367    // Basic implementation of the Greeter service
368    #[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    // Build the server with services
419    let mut router = server_builder.add_service(GreeterServer::new(greeter));
420
421    // Add reflection service if enabled
422    if config.enable_reflection {
423        // Build reflection service from the descriptor pool
424        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// start_combined_server removed - was a stub that was never implemented
446
447#[cfg(test)]
448mod tests {
449
450    #[test]
451    fn test_module_compiles() {}
452}