ipfrs_interface/
gateway.rs

1//! HTTP Gateway for IPFRS
2//!
3//! Provides REST API endpoints for interacting with IPFRS storage.
4
5use async_graphql::http::{playground_source, GraphQLPlaygroundConfig};
6use async_graphql_axum::{GraphQLRequest, GraphQLResponse};
7use axum::{
8    body::Body,
9    extract::{Multipart, Path, State},
10    http::{header, HeaderMap, StatusCode},
11    response::{Html, IntoResponse, Response},
12    routing::{get, post},
13    Json, Router,
14};
15use ipfrs_core::{Cid, Result as CoreResult};
16use ipfrs_semantic::{DistanceMetric, QueryFilter, RouterConfig, SemanticRouter};
17use ipfrs_storage::{BlockStoreConfig, BlockStoreTrait, SledBlockStore};
18use ipfrs_tensorlogic::{Predicate, Proof, Rule, Substitution, TensorLogicStore, Term};
19use serde::{Deserialize, Serialize};
20use std::sync::Arc;
21use tower_http::compression::CompressionLayer;
22use tower_http::trace::TraceLayer;
23use tracing::{error, info};
24
25use crate::middleware::{
26    add_caching_headers, check_etag_match, not_modified_response, CacheConfig,
27};
28
29use crate::auth::AuthState;
30use crate::auth_handlers;
31use crate::graphql::{create_schema, IpfrsSchema};
32use crate::streaming;
33use crate::tensor;
34use crate::tls::TlsConfig;
35
36/// Gateway server state
37#[derive(Clone)]
38pub struct GatewayState {
39    pub(crate) store: Arc<SledBlockStore>,
40    semantic: Option<Arc<SemanticRouter>>,
41    tensorlogic: Option<Arc<TensorLogicStore<SledBlockStore>>>,
42    network: Option<Arc<tokio::sync::Mutex<ipfrs_network::NetworkNode>>>,
43    graphql_schema: Option<IpfrsSchema>,
44    pub(crate) auth: Option<AuthState>,
45}
46
47impl GatewayState {
48    /// Create a new gateway state with the given storage configuration
49    pub fn new(config: BlockStoreConfig) -> CoreResult<Self> {
50        let store = SledBlockStore::new(config)?;
51        Ok(Self {
52            store: Arc::new(store),
53            semantic: None,
54            tensorlogic: None,
55            network: None,
56            graphql_schema: None,
57            auth: None,
58        })
59    }
60
61    /// Enable authentication and authorization
62    pub fn with_auth(
63        mut self,
64        secret: &[u8],
65        default_admin_password: Option<&str>,
66    ) -> CoreResult<Self> {
67        let auth_state = if let Some(password) = default_admin_password {
68            AuthState::with_default_admin(secret, password).map_err(|e| {
69                ipfrs_core::Error::Internal(format!("Failed to create auth state: {}", e))
70            })?
71        } else {
72            AuthState::new(secret)
73        };
74        self.auth = Some(auth_state);
75        Ok(self)
76    }
77
78    /// Enable semantic search capabilities
79    pub fn with_semantic(mut self, config: RouterConfig) -> CoreResult<Self> {
80        let semantic = SemanticRouter::new(config).map_err(|e| {
81            ipfrs_core::Error::Internal(format!("Failed to create semantic router: {}", e))
82        })?;
83        self.semantic = Some(Arc::new(semantic));
84        Ok(self)
85    }
86
87    /// Enable tensorlogic capabilities
88    pub fn with_tensorlogic(mut self) -> CoreResult<Self> {
89        let tensorlogic = TensorLogicStore::new(Arc::clone(&self.store))?;
90        self.tensorlogic = Some(Arc::new(tensorlogic));
91        Ok(self)
92    }
93
94    /// Enable networking capabilities
95    pub fn with_network(mut self, network: ipfrs_network::NetworkNode) -> Self {
96        self.network = Some(Arc::new(tokio::sync::Mutex::new(network)));
97        self
98    }
99
100    /// Enable GraphQL API
101    pub fn with_graphql(mut self) -> Self {
102        let schema = create_schema(
103            Arc::clone(&self.store),
104            self.semantic.clone(),
105            self.tensorlogic.clone(),
106        );
107        self.graphql_schema = Some(schema);
108        self
109    }
110}
111
112/// Gateway server configuration
113#[derive(Debug, Clone)]
114pub struct GatewayConfig {
115    /// Listen address
116    pub listen_addr: String,
117    /// Storage configuration
118    pub storage_config: BlockStoreConfig,
119    /// Optional TLS configuration for HTTPS
120    pub tls_config: Option<TlsConfig>,
121    /// Compression configuration
122    pub compression_config: crate::middleware::CompressionConfig,
123}
124
125impl Default for GatewayConfig {
126    fn default() -> Self {
127        Self {
128            listen_addr: "127.0.0.1:8080".to_string(),
129            storage_config: BlockStoreConfig::default(),
130            tls_config: None,
131            compression_config: crate::middleware::CompressionConfig::default(),
132        }
133    }
134}
135
136impl GatewayConfig {
137    /// Create a production-ready configuration
138    ///
139    /// Features:
140    /// - Listens on all interfaces (0.0.0.0:8080)
141    /// - Maximum compression enabled (best ratio)
142    /// - Larger cache (500MB)
143    /// - Optimized for throughput
144    pub fn production() -> Self {
145        Self {
146            listen_addr: "0.0.0.0:8080".to_string(),
147            storage_config: BlockStoreConfig::default()
148                .with_path("./ipfrs_data".into())
149                .with_cache_mb(500),
150            tls_config: None,
151            compression_config: crate::middleware::CompressionConfig {
152                enable_gzip: true,
153                enable_brotli: true,
154                enable_deflate: true,
155                level: crate::middleware::CompressionLevel::Best,
156                min_size: 512,
157            },
158        }
159    }
160
161    /// Create a development configuration
162    ///
163    /// Features:
164    /// - Listens on localhost only (127.0.0.1:8080)
165    /// - Fast compression (minimal CPU usage)
166    /// - Smaller cache (50MB)
167    /// - Optimized for quick iteration
168    pub fn development() -> Self {
169        Self {
170            listen_addr: "127.0.0.1:8080".to_string(),
171            storage_config: BlockStoreConfig::default()
172                .with_path("./dev_data".into())
173                .with_cache_mb(50),
174            tls_config: None,
175            compression_config: crate::middleware::CompressionConfig {
176                enable_gzip: true,
177                enable_brotli: false,
178                enable_deflate: false,
179                level: crate::middleware::CompressionLevel::Fastest,
180                min_size: 2048,
181            },
182        }
183    }
184
185    /// Create a testing configuration
186    ///
187    /// Features:
188    /// - Listens on localhost with random port (127.0.0.1:0)
189    /// - Compression disabled for faster tests
190    /// - Minimal cache (10MB)
191    /// - In-memory or temporary storage
192    pub fn testing() -> Self {
193        Self {
194            listen_addr: "127.0.0.1:0".to_string(),
195            storage_config: BlockStoreConfig::default()
196                .with_path("/tmp/ipfrs_test".into())
197                .with_cache_mb(10),
198            tls_config: None,
199            compression_config: crate::middleware::CompressionConfig {
200                enable_gzip: false,
201                enable_brotli: false,
202                enable_deflate: false,
203                level: crate::middleware::CompressionLevel::Fastest,
204                min_size: 1048576, // Only compress files > 1MB
205            },
206        }
207    }
208
209    /// Builder: Set the listen address
210    pub fn with_listen_addr(mut self, addr: impl Into<String>) -> Self {
211        self.listen_addr = addr.into();
212        self
213    }
214
215    /// Builder: Set the storage path
216    pub fn with_storage_path(mut self, path: impl Into<String>) -> Self {
217        self.storage_config = self.storage_config.with_path(path.into().into());
218        self
219    }
220
221    /// Builder: Set the cache size in MB
222    pub fn with_cache_mb(mut self, size_mb: usize) -> Self {
223        self.storage_config = self.storage_config.with_cache_mb(size_mb);
224        self
225    }
226
227    /// Builder: Enable TLS/HTTPS
228    pub fn with_tls(mut self, tls_config: TlsConfig) -> Self {
229        self.tls_config = Some(tls_config);
230        self
231    }
232
233    /// Builder: Set compression level
234    pub fn with_compression_level(mut self, level: crate::middleware::CompressionLevel) -> Self {
235        self.compression_config.level = level;
236        self
237    }
238
239    /// Builder: Enable all compression formats
240    pub fn with_full_compression(mut self) -> Self {
241        self.compression_config.enable_gzip = true;
242        self.compression_config.enable_brotli = true;
243        self.compression_config.enable_deflate = true;
244        self
245    }
246
247    /// Builder: Disable all compression
248    pub fn without_compression(mut self) -> Self {
249        self.compression_config.enable_gzip = false;
250        self.compression_config.enable_brotli = false;
251        self.compression_config.enable_deflate = false;
252        self
253    }
254
255    /// Validate the configuration
256    ///
257    /// Returns an error if the configuration is invalid
258    pub fn validate(&self) -> CoreResult<()> {
259        // Validate listen address format
260        if self.listen_addr.is_empty() {
261            return Err(ipfrs_core::Error::Internal(
262                "Listen address cannot be empty".to_string(),
263            ));
264        }
265
266        // Validate that listen address can be parsed
267        self.listen_addr
268            .parse::<std::net::SocketAddr>()
269            .map_err(|e| ipfrs_core::Error::Internal(format!("Invalid listen address: {}", e)))?;
270
271        // Validate storage path
272        if self.storage_config.path.as_os_str().is_empty() {
273            return Err(ipfrs_core::Error::Internal(
274                "Storage path cannot be empty".to_string(),
275            ));
276        }
277
278        // Validate compression config
279        if self.compression_config.min_size == 0 {
280            return Err(ipfrs_core::Error::Internal(
281                "Compression min_size must be greater than 0".to_string(),
282            ));
283        }
284
285        Ok(())
286    }
287}
288
289/// HTTP Gateway server
290pub struct Gateway {
291    config: GatewayConfig,
292    state: GatewayState,
293}
294
295impl Gateway {
296    /// Create a new gateway with the given configuration
297    pub fn new(config: GatewayConfig) -> CoreResult<Self> {
298        let state = GatewayState::new(config.storage_config.clone())?;
299        Ok(Self { config, state })
300    }
301
302    /// Build the router with all endpoints
303    fn router(&self) -> Router {
304        let mut router = Router::new()
305            // Health check (public)
306            .route("/health", get(health_check))
307            // Prometheus metrics (public)
308            .route("/metrics", get(metrics_endpoint))
309            // IPFS gateway (public for now)
310            .route("/ipfs/:cid", get(get_content))
311            // Authentication endpoints (public)
312            .route("/api/v0/auth/login", post(auth_handlers::login_handler))
313            .route(
314                "/api/v0/auth/register",
315                post(auth_handlers::register_handler),
316            )
317            // GraphQL API (public for now)
318            .route("/graphql", post(graphql_handler))
319            .route("/graphql", get(graphql_playground))
320            // Kubo-compatible API
321            .route("/api/v0/version", get(api_version))
322            .route("/api/v0/add", post(api_add))
323            .route("/api/v0/block/get", post(api_block_get))
324            .route("/api/v0/block/put", post(api_block_put))
325            .route("/api/v0/block/stat", post(api_block_stat))
326            .route("/api/v0/cat", post(api_cat))
327            .route("/api/v0/dag/get", post(api_dag_get))
328            .route("/api/v0/dag/put", post(api_dag_put))
329            .route("/api/v0/dag/resolve", post(api_dag_resolve))
330            // Semantic search endpoints
331            .route("/api/v0/semantic/index", post(api_semantic_index))
332            .route("/api/v0/semantic/search", post(api_semantic_search))
333            .route("/api/v0/semantic/stats", get(api_semantic_stats))
334            .route("/api/v0/semantic/save", post(api_semantic_save))
335            .route("/api/v0/semantic/load", post(api_semantic_load))
336            // TensorLogic endpoints
337            .route("/api/v0/logic/term", post(api_logic_store_term))
338            .route("/api/v0/logic/term/:cid", get(api_logic_get_term))
339            .route("/api/v0/logic/predicate", post(api_logic_store_predicate))
340            .route("/api/v0/logic/rule", post(api_logic_store_rule))
341            .route("/api/v0/logic/stats", get(api_logic_stats))
342            .route("/api/v0/logic/fact", post(api_logic_add_fact))
343            .route("/api/v0/logic/rule/add", post(api_logic_add_rule))
344            .route("/api/v0/logic/infer", post(api_logic_infer))
345            .route("/api/v0/logic/prove", post(api_logic_prove))
346            .route("/api/v0/logic/verify", post(api_logic_verify))
347            .route("/api/v0/logic/proof/:cid", get(api_logic_get_proof))
348            .route("/api/v0/logic/kb/stats", get(api_logic_kb_stats))
349            .route("/api/v0/logic/kb/save", post(api_logic_kb_save))
350            .route("/api/v0/logic/kb/load", post(api_logic_kb_load))
351            // Network endpoints
352            .route("/api/v0/id", get(api_network_id))
353            .route("/api/v0/swarm/peers", get(api_swarm_peers))
354            .route("/api/v0/swarm/connect", post(api_swarm_connect))
355            .route("/api/v0/swarm/disconnect", post(api_swarm_disconnect))
356            .route("/api/v0/dht/findprovs", post(api_dht_findprovs))
357            .route("/api/v0/dht/provide", post(api_dht_provide))
358            // Streaming API (v1)
359            .route("/v1/stream/download/:cid", get(streaming::stream_download))
360            .route("/v1/stream/upload", post(streaming::stream_upload))
361            .route(
362                "/v1/progress/:operation_id",
363                get(streaming::progress_stream),
364            )
365            // Batch API (v1)
366            .route("/v1/block/batch/get", post(streaming::batch_get))
367            .route("/v1/block/batch/put", post(streaming::batch_put))
368            .route("/v1/block/batch/has", post(streaming::batch_has))
369            // Zero-Copy Tensor API (v1)
370            .route("/v1/tensor/:cid", get(tensor::get_tensor))
371            .route("/v1/tensor/:cid/info", get(tensor::get_tensor_info))
372            .route("/v1/tensor/:cid/arrow", get(tensor::get_tensor_arrow));
373
374        // Add protected auth endpoints if auth is enabled
375        if self.state.auth.is_some() {
376            router = router
377                .route("/api/v0/auth/me", get(auth_handlers::me_handler))
378                .route(
379                    "/api/v0/auth/permissions",
380                    post(auth_handlers::update_permissions_handler),
381                )
382                .route(
383                    "/api/v0/auth/deactivate/:username",
384                    post(auth_handlers::deactivate_user_handler),
385                )
386                // API Key management endpoints
387                .route(
388                    "/api/v0/auth/keys",
389                    post(auth_handlers::create_api_key_handler),
390                )
391                .route(
392                    "/api/v0/auth/keys",
393                    get(auth_handlers::list_api_keys_handler),
394                )
395                .route(
396                    "/api/v0/auth/keys/:key_id/revoke",
397                    post(auth_handlers::revoke_api_key_handler),
398                )
399                .route(
400                    "/api/v0/auth/keys/:key_id",
401                    axum::routing::delete(auth_handlers::delete_api_key_handler),
402                );
403        }
404
405        // Apply compression layer
406        // Note: tower-http's CompressionLayer uses default compression levels.
407        // The compression_config provides configuration for documentation and future enhancements.
408        // Current behavior: gzip, brotli, and deflate with default levels.
409        let router = if self.config.compression_config.enable_gzip
410            || self.config.compression_config.enable_brotli
411            || self.config.compression_config.enable_deflate
412        {
413            router.layer(CompressionLayer::new())
414        } else {
415            router
416        };
417
418        router
419            .with_state(self.state.clone())
420            .layer(TraceLayer::new_for_http())
421    }
422
423    /// Start the gateway server (HTTP or HTTPS based on configuration)
424    pub async fn start(self) -> CoreResult<()> {
425        let app = self.router();
426
427        // Print endpoint information
428        self.print_endpoints();
429
430        // Start HTTP or HTTPS server based on TLS configuration
431        if let Some(ref tls_config) = self.config.tls_config {
432            info!(
433                "Starting IPFRS HTTPS Gateway on {}",
434                self.config.listen_addr
435            );
436
437            // Build TLS server config
438            let rustls_config = tls_config.build_server_config().await.map_err(|e| {
439                ipfrs_core::Error::Internal(format!("TLS configuration error: {}", e))
440            })?;
441
442            // Create TLS acceptor
443            let addr: std::net::SocketAddr = self
444                .config
445                .listen_addr
446                .parse()
447                .map_err(|e| ipfrs_core::Error::Internal(format!("Invalid address: {}", e)))?;
448
449            info!("Gateway listening on https://{}", self.config.listen_addr);
450            info!("TLS/SSL enabled");
451
452            // Start HTTPS server with axum-server
453            axum_server::bind_rustls(addr, rustls_config)
454                .serve(app.into_make_service())
455                .await
456                .map_err(|e| ipfrs_core::Error::Internal(format!("HTTPS server error: {}", e)))?;
457        } else {
458            info!("Starting IPFRS HTTP Gateway on {}", self.config.listen_addr);
459
460            let listener = tokio::net::TcpListener::bind(&self.config.listen_addr)
461                .await
462                .map_err(|e| {
463                    ipfrs_core::Error::Internal(format!("Failed to bind to address: {}", e))
464                })?;
465
466            info!("Gateway listening on http://{}", self.config.listen_addr);
467            info!("Warning: TLS not enabled, using plain HTTP");
468
469            // Start HTTP server
470            axum::serve(listener, app)
471                .await
472                .map_err(|e| ipfrs_core::Error::Internal(format!("HTTP server error: {}", e)))?;
473        }
474
475        Ok(())
476    }
477
478    /// Print available endpoints
479    fn print_endpoints(&self) {
480        info!("Endpoints:");
481        info!("  GET  /health                      - Health check");
482        info!("  GET  /ipfs/{{cid}}                 - Retrieve content");
483
484        // Authentication endpoints (if enabled)
485        if self.state.auth.is_some() {
486            info!("  POST /api/v0/auth/login           - User login");
487            info!("  POST /api/v0/auth/register        - User registration");
488            info!("  GET  /api/v0/auth/me              - Current user info");
489            info!("  POST /api/v0/auth/permissions     - Update permissions (admin)");
490            info!("  POST /api/v0/auth/deactivate/:user - Deactivate user (admin)");
491            info!("  POST /api/v0/auth/keys            - Create API key");
492            info!("  GET  /api/v0/auth/keys            - List API keys");
493            info!("  POST /api/v0/auth/keys/:id/revoke - Revoke API key");
494            info!("  DEL  /api/v0/auth/keys/:id        - Delete API key");
495        }
496
497        info!("  POST /api/v0/version              - Get version");
498        info!("  POST /api/v0/add                  - Upload file");
499        info!("  POST /api/v0/block/get            - Get block");
500        info!("  POST /api/v0/block/put            - Store raw block");
501        info!("  POST /api/v0/block/stat           - Get block stats");
502        info!("  POST /api/v0/cat                  - Output content");
503        info!("  POST /api/v0/dag/get              - Get DAG node");
504        info!("  POST /api/v0/dag/put              - Store DAG node");
505        info!("  POST /api/v0/dag/resolve          - Resolve IPLD path");
506        info!("  POST /api/v0/semantic/index       - Index content");
507        info!("  POST /api/v0/semantic/search      - Search similar");
508        info!("  GET  /api/v0/semantic/stats       - Semantic stats");
509        info!("  POST /api/v0/semantic/save        - Save semantic index");
510        info!("  POST /api/v0/semantic/load        - Load semantic index");
511        info!("  POST /api/v0/logic/term           - Store term");
512        info!("  GET  /api/v0/logic/term/{{cid}}    - Get term");
513        info!("  POST /api/v0/logic/predicate      - Store predicate");
514        info!("  POST /api/v0/logic/rule           - Store rule");
515        info!("  GET  /api/v0/logic/stats          - Logic stats");
516        info!("  POST /api/v0/logic/kb/save        - Save knowledge base");
517        info!("  POST /api/v0/logic/kb/load        - Load knowledge base");
518        info!("  GET  /api/v0/id                   - Show peer ID");
519        info!("  GET  /api/v0/swarm/peers          - List peers");
520        info!("  POST /api/v0/swarm/connect        - Connect to peer");
521        info!("  POST /api/v0/swarm/disconnect     - Disconnect peer");
522        info!("  POST /api/v0/dht/findprovs        - Find providers");
523        info!("  POST /api/v0/dht/provide          - Announce content");
524
525        // Streaming API (v1)
526        info!("  GET  /v1/stream/download/:cid     - Stream download");
527        info!("  POST /v1/stream/upload            - Stream upload");
528        info!("  GET  /v1/progress/:operation_id   - SSE progress");
529
530        // Batch API (v1)
531        info!("  POST /v1/block/batch/get          - Batch get blocks");
532        info!("  POST /v1/block/batch/put          - Batch put blocks");
533        info!("  POST /v1/block/batch/has          - Batch check blocks");
534    }
535
536    /// Enable GraphQL API
537    pub fn with_graphql(mut self) -> Self {
538        self.state = self.state.with_graphql();
539        self
540    }
541
542    /// Enable authentication and authorization
543    pub fn with_auth(
544        mut self,
545        secret: &[u8],
546        default_admin_password: Option<&str>,
547    ) -> CoreResult<Self> {
548        self.state = self.state.with_auth(secret, default_admin_password)?;
549        Ok(self)
550    }
551
552    /// Enable semantic search capabilities
553    pub fn with_semantic(mut self, config: RouterConfig) -> CoreResult<Self> {
554        self.state = self.state.with_semantic(config)?;
555        Ok(self)
556    }
557
558    /// Enable tensorlogic capabilities
559    pub fn with_tensorlogic(mut self) -> CoreResult<Self> {
560        self.state = self.state.with_tensorlogic()?;
561        Ok(self)
562    }
563
564    /// Enable networking capabilities
565    pub fn with_network(mut self, network: ipfrs_network::NetworkNode) -> Self {
566        self.state = self.state.with_network(network);
567        self
568    }
569}
570
571// ============================================================================
572// Handler Functions
573// ============================================================================
574
575/// Health check endpoint
576async fn health_check() -> impl IntoResponse {
577    Json(serde_json::json!({
578        "status": "ok",
579        "service": "ipfrs-gateway",
580        "version": env!("CARGO_PKG_VERSION")
581    }))
582}
583
584/// Prometheus metrics endpoint
585///
586/// Returns metrics in Prometheus text exposition format for scraping
587async fn metrics_endpoint() -> impl IntoResponse {
588    match crate::metrics::encode_metrics() {
589        Ok(metrics) => (
590            StatusCode::OK,
591            [(header::CONTENT_TYPE, "text/plain; version=0.0.4")],
592            metrics,
593        )
594            .into_response(),
595        Err(e) => (
596            StatusCode::INTERNAL_SERVER_ERROR,
597            format!("Failed to encode metrics: {}", e),
598        )
599            .into_response(),
600    }
601}
602
603/// Get content via IPFS gateway with range request support
604///
605/// Supports:
606/// - Single range requests (Range: bytes=0-100)
607/// - Multi-range requests (Range: bytes=0-100,200-300)
608/// - Conditional requests (If-None-Match)
609/// - Caching headers (ETag, Cache-Control)
610async fn get_content(
611    State(state): State<GatewayState>,
612    Path(cid_str): Path<String>,
613    headers: HeaderMap,
614) -> Result<Response, AppError> {
615    let cid: Cid = cid_str
616        .parse()
617        .map_err(|_| AppError::InvalidCid(cid_str.clone()))?;
618
619    // Cache configuration for CID-based content
620    let cache_config = CacheConfig::default();
621
622    // Check for conditional request (If-None-Match)
623    if check_etag_match(&headers, &cid_str) {
624        return Ok(not_modified_response(&cid_str, &cache_config));
625    }
626
627    match state.store.get(&cid).await? {
628        Some(block) => {
629            let data = block.data();
630            let total_size = data.len();
631
632            // Check for Range header
633            if let Some(range_header) = headers.get(header::RANGE) {
634                if let Ok(range_str) = range_header.to_str() {
635                    // Try parsing multi-range first
636                    if let Some(ranges) = parse_multi_range(range_str, total_size) {
637                        if ranges.len() == 1 {
638                            // Single range - simple response
639                            let (start, end) = ranges[0];
640                            let slice = &data[start..end];
641                            let content_range =
642                                format!("bytes {}-{}/{}", start, end - 1, total_size);
643
644                            let mut response = Response::builder()
645                                .status(StatusCode::PARTIAL_CONTENT)
646                                .header(header::CONTENT_RANGE, content_range)
647                                .header(header::CONTENT_LENGTH, slice.len().to_string())
648                                .header(header::CONTENT_TYPE, "application/octet-stream")
649                                .header(header::ACCEPT_RANGES, "bytes")
650                                .body(Body::from(slice.to_vec()))
651                                .unwrap();
652
653                            // Add caching headers
654                            add_caching_headers(response.headers_mut(), &cid_str, &cache_config);
655
656                            return Ok(response);
657                        } else {
658                            // Multi-range response with multipart/byteranges
659                            return Ok(build_multipart_response(
660                                data,
661                                &ranges,
662                                total_size,
663                                &cid_str,
664                                &cache_config,
665                            ));
666                        }
667                    }
668                }
669            }
670
671            // No range request or invalid range, return full content
672            let mut response = Response::builder()
673                .status(StatusCode::OK)
674                .header(header::CONTENT_TYPE, "application/octet-stream")
675                .header(header::CONTENT_LENGTH, total_size.to_string())
676                .header(header::ACCEPT_RANGES, "bytes")
677                .body(Body::from(data.to_vec()))
678                .unwrap();
679
680            // Add caching headers
681            add_caching_headers(response.headers_mut(), &cid_str, &cache_config);
682
683            Ok(response)
684        }
685        None => Err(AppError::BlockNotFound(cid_str)),
686    }
687}
688
689/// Parse HTTP Range header for single range
690/// Returns (start, end) where end is exclusive
691#[allow(dead_code)]
692fn parse_range(range_str: &str, total_size: usize) -> Option<(usize, usize)> {
693    // Expected format: "bytes=start-end" or "bytes=start-"
694    let range_str = range_str.strip_prefix("bytes=")?;
695
696    if let Some((start_str, end_str)) = range_str.split_once('-') {
697        let start: usize = start_str.parse().ok()?;
698
699        let end = if end_str.is_empty() {
700            total_size
701        } else {
702            end_str.parse::<usize>().ok()? + 1
703        };
704
705        if start < total_size && start < end && end <= total_size {
706            Some((start, end))
707        } else {
708            None
709        }
710    } else {
711        None
712    }
713}
714
715/// Parse HTTP Range header for multiple ranges
716/// Returns Vec of (start, end) tuples where end is exclusive
717/// Supports formats: "bytes=0-100", "bytes=0-100,200-300", "bytes=0-100, 200-300"
718fn parse_multi_range(range_str: &str, total_size: usize) -> Option<Vec<(usize, usize)>> {
719    let range_str = range_str.strip_prefix("bytes=")?;
720
721    let mut ranges = Vec::new();
722
723    for part in range_str.split(',') {
724        let part = part.trim();
725        if let Some((start_str, end_str)) = part.split_once('-') {
726            // Handle suffix range (e.g., "-500" means last 500 bytes)
727            if start_str.is_empty() {
728                let suffix_len: usize = end_str.parse().ok()?;
729                let start = total_size.saturating_sub(suffix_len);
730                ranges.push((start, total_size));
731                continue;
732            }
733
734            let start: usize = start_str.parse().ok()?;
735
736            let end = if end_str.is_empty() {
737                total_size
738            } else {
739                end_str.parse::<usize>().ok()? + 1
740            };
741
742            if start < total_size && start < end && end <= total_size {
743                ranges.push((start, end));
744            } else {
745                return None; // Invalid range
746            }
747        } else {
748            return None; // Invalid format
749        }
750    }
751
752    if ranges.is_empty() {
753        None
754    } else {
755        // Merge overlapping and adjacent ranges for efficiency
756        Some(merge_ranges(ranges))
757    }
758}
759
760/// Merge overlapping and adjacent ranges
761fn merge_ranges(mut ranges: Vec<(usize, usize)>) -> Vec<(usize, usize)> {
762    if ranges.len() <= 1 {
763        return ranges;
764    }
765
766    // Sort by start position
767    ranges.sort_by_key(|r| r.0);
768
769    let mut merged = Vec::new();
770    let mut current = ranges[0];
771
772    for range in ranges.into_iter().skip(1) {
773        // Check if ranges overlap or are adjacent
774        if range.0 <= current.1 {
775            // Extend current range
776            current.1 = current.1.max(range.1);
777        } else {
778            merged.push(current);
779            current = range;
780        }
781    }
782    merged.push(current);
783
784    merged
785}
786
787/// Build multipart/byteranges response for multi-range requests
788fn build_multipart_response(
789    data: &[u8],
790    ranges: &[(usize, usize)],
791    total_size: usize,
792    cid: &str,
793    cache_config: &CacheConfig,
794) -> Response {
795    // Generate a boundary string
796    let boundary = format!("ipfrs_boundary_{:x}", rand::random::<u64>());
797
798    // Build multipart body
799    let mut body = Vec::new();
800
801    for (start, end) in ranges {
802        // Part header
803        body.extend_from_slice(format!("--{}\r\n", boundary).as_bytes());
804        body.extend_from_slice(b"Content-Type: application/octet-stream\r\n");
805        body.extend_from_slice(
806            format!(
807                "Content-Range: bytes {}-{}/{}\r\n\r\n",
808                start,
809                end - 1,
810                total_size
811            )
812            .as_bytes(),
813        );
814
815        // Part data
816        body.extend_from_slice(&data[*start..*end]);
817        body.extend_from_slice(b"\r\n");
818    }
819
820    // Final boundary
821    body.extend_from_slice(format!("--{}--\r\n", boundary).as_bytes());
822
823    let content_type = format!("multipart/byteranges; boundary={}", boundary);
824
825    let mut response = Response::builder()
826        .status(StatusCode::PARTIAL_CONTENT)
827        .header(header::CONTENT_TYPE, content_type)
828        .header(header::CONTENT_LENGTH, body.len().to_string())
829        .header(header::ACCEPT_RANGES, "bytes")
830        .body(Body::from(body))
831        .unwrap();
832
833    // Add caching headers
834    add_caching_headers(response.headers_mut(), cid, cache_config);
835
836    response
837}
838
839/// Version endpoint
840#[derive(Serialize)]
841struct VersionResponse {
842    #[serde(rename = "Version")]
843    version: String,
844    #[serde(rename = "System")]
845    system: String,
846}
847
848async fn api_version() -> impl IntoResponse {
849    Json(VersionResponse {
850        version: env!("CARGO_PKG_VERSION").to_string(),
851        system: "ipfrs/0.3.0".to_string(),
852    })
853}
854
855/// Block get request
856#[derive(Deserialize)]
857struct BlockRequest {
858    arg: String,
859}
860
861async fn api_block_get(
862    State(state): State<GatewayState>,
863    Json(req): Json<BlockRequest>,
864) -> Result<Response, AppError> {
865    let cid: Cid = req
866        .arg
867        .parse()
868        .map_err(|_| AppError::InvalidCid(req.arg.clone()))?;
869
870    match state.store.get(&cid).await? {
871        Some(block) => Ok((StatusCode::OK, block.data().to_vec()).into_response()),
872        None => Err(AppError::BlockNotFound(req.arg)),
873    }
874}
875
876/// Block stat response
877#[derive(Serialize)]
878struct BlockStatResponse {
879    #[serde(rename = "Key")]
880    key: String,
881    #[serde(rename = "Size")]
882    size: u64,
883}
884
885async fn api_block_stat(
886    State(state): State<GatewayState>,
887    Json(req): Json<BlockRequest>,
888) -> Result<Json<BlockStatResponse>, AppError> {
889    let cid: Cid = req
890        .arg
891        .parse()
892        .map_err(|_| AppError::InvalidCid(req.arg.clone()))?;
893
894    match state.store.get(&cid).await? {
895        Some(block) => Ok(Json(BlockStatResponse {
896            key: req.arg,
897            size: block.size(),
898        })),
899        None => Err(AppError::BlockNotFound(req.arg)),
900    }
901}
902
903async fn api_cat(
904    State(state): State<GatewayState>,
905    Json(req): Json<BlockRequest>,
906) -> Result<Response, AppError> {
907    let cid: Cid = req
908        .arg
909        .parse()
910        .map_err(|_| AppError::InvalidCid(req.arg.clone()))?;
911
912    match state.store.get(&cid).await? {
913        Some(block) => Ok((StatusCode::OK, block.data().to_vec()).into_response()),
914        None => Err(AppError::BlockNotFound(req.arg)),
915    }
916}
917
918/// Add response
919#[derive(Serialize)]
920struct AddResponse {
921    #[serde(rename = "Name")]
922    name: String,
923    #[serde(rename = "Hash")]
924    hash: String,
925    #[serde(rename = "Size")]
926    size: String,
927}
928
929async fn api_add(
930    State(state): State<GatewayState>,
931    mut multipart: Multipart,
932) -> Result<Json<AddResponse>, AppError> {
933    use bytes::Bytes;
934    use ipfrs_core::Block;
935
936    // Process the first file field
937    if let Some(field) = multipart
938        .next_field()
939        .await
940        .map_err(|e| AppError::Upload(format!("Failed to read multipart field: {}", e)))?
941    {
942        let name = field.file_name().unwrap_or("upload").to_string();
943        let data = field
944            .bytes()
945            .await
946            .map_err(|e| AppError::Upload(format!("Failed to read file data: {}", e)))?;
947
948        // Create block from uploaded data
949        let bytes_data = Bytes::from(data.to_vec());
950        let block = Block::new(bytes_data)
951            .map_err(|e| AppError::Upload(format!("Failed to create block: {}", e)))?;
952        let cid = *block.cid();
953        let size = block.size();
954
955        // Store block
956        state.store.put(&block).await?;
957
958        info!("Added file '{}' as {}", name, cid);
959
960        Ok(Json(AddResponse {
961            name,
962            hash: cid.to_string(),
963            size: size.to_string(),
964        }))
965    } else {
966        Err(AppError::Upload("No file provided".to_string()))
967    }
968}
969
970/// Block put request with raw data
971async fn api_block_put(
972    State(state): State<GatewayState>,
973    body: axum::body::Bytes,
974) -> Result<Json<AddResponse>, AppError> {
975    use ipfrs_core::Block;
976
977    // Create block from raw bytes
978    let block =
979        Block::new(body).map_err(|e| AppError::Upload(format!("Failed to create block: {}", e)))?;
980    let cid = *block.cid();
981    let size = block.size();
982
983    // Store block
984    state.store.put(&block).await?;
985
986    info!("Stored raw block {}", cid);
987
988    Ok(Json(AddResponse {
989        name: cid.to_string(),
990        hash: cid.to_string(),
991        size: size.to_string(),
992    }))
993}
994
995/// DAG get request
996#[derive(Deserialize)]
997struct DagRequest {
998    arg: String,
999}
1000
1001/// DAG get response
1002#[derive(Serialize)]
1003struct DagGetResponse {
1004    #[serde(rename = "Data")]
1005    data: String,
1006}
1007
1008async fn api_dag_get(
1009    State(state): State<GatewayState>,
1010    Json(req): Json<DagRequest>,
1011) -> Result<Json<DagGetResponse>, AppError> {
1012    let cid: Cid = req
1013        .arg
1014        .parse()
1015        .map_err(|_| AppError::InvalidCid(req.arg.clone()))?;
1016
1017    match state.store.get(&cid).await? {
1018        Some(block) => {
1019            // Convert block data to base64 for JSON transport
1020            let data_base64 =
1021                base64::Engine::encode(&base64::engine::general_purpose::STANDARD, block.data());
1022
1023            Ok(Json(DagGetResponse { data: data_base64 }))
1024        }
1025        None => Err(AppError::BlockNotFound(req.arg)),
1026    }
1027}
1028
1029/// DAG put response
1030#[derive(Serialize)]
1031struct DagPutResponse {
1032    #[serde(rename = "Cid")]
1033    cid: CidInfo,
1034}
1035
1036#[derive(Serialize)]
1037struct CidInfo {
1038    #[serde(rename = "/")]
1039    cid: String,
1040}
1041
1042async fn api_dag_put(
1043    State(state): State<GatewayState>,
1044    body: axum::body::Bytes,
1045) -> Result<Json<DagPutResponse>, AppError> {
1046    use ipfrs_core::Block;
1047
1048    // Create block from DAG data
1049    let block = Block::new(body)
1050        .map_err(|e| AppError::Upload(format!("Failed to create DAG block: {}", e)))?;
1051    let cid = *block.cid();
1052
1053    // Store block
1054    state.store.put(&block).await?;
1055
1056    info!("Stored DAG node {}", cid);
1057
1058    Ok(Json(DagPutResponse {
1059        cid: CidInfo {
1060            cid: cid.to_string(),
1061        },
1062    }))
1063}
1064
1065/// DAG resolve request
1066#[derive(Deserialize)]
1067struct DagResolveRequest {
1068    arg: String,
1069}
1070
1071/// DAG resolve response
1072#[derive(Serialize)]
1073struct DagResolveResponse {
1074    #[serde(rename = "Cid")]
1075    cid: CidInfo,
1076    #[serde(rename = "RemPath")]
1077    rem_path: String,
1078}
1079
1080async fn api_dag_resolve(
1081    State(_state): State<GatewayState>,
1082    Json(req): Json<DagResolveRequest>,
1083) -> Result<Json<DagResolveResponse>, AppError> {
1084    // Parse the path (e.g., "/ipfs/Qm.../path/to/data")
1085    let path = req.arg.trim_start_matches("/ipfs/");
1086    let parts: Vec<&str> = path.splitn(2, '/').collect();
1087
1088    let cid_str = parts[0];
1089    let cid: Cid = cid_str
1090        .parse()
1091        .map_err(|_| AppError::InvalidCid(cid_str.to_string()))?;
1092
1093    let sub_path = if parts.len() > 1 { parts[1] } else { "" };
1094
1095    // For now, we just return the root CID and the remainder path
1096    // Full IPLD path resolution would require parsing DAG-CBOR/JSON
1097    Ok(Json(DagResolveResponse {
1098        cid: CidInfo {
1099            cid: cid.to_string(),
1100        },
1101        rem_path: sub_path.to_string(),
1102    }))
1103}
1104
1105// ============================================================================
1106// Semantic Search Handlers
1107// ============================================================================
1108
1109/// Semantic index request
1110#[derive(Deserialize)]
1111struct SemanticIndexRequest {
1112    cid: String,
1113    embedding: Vec<f32>,
1114}
1115
1116/// Semantic index response
1117#[derive(Serialize)]
1118struct SemanticIndexResponse {
1119    indexed: bool,
1120}
1121
1122async fn api_semantic_index(
1123    State(state): State<GatewayState>,
1124    Json(req): Json<SemanticIndexRequest>,
1125) -> Result<Json<SemanticIndexResponse>, AppError> {
1126    let semantic = state
1127        .semantic
1128        .as_ref()
1129        .ok_or_else(|| AppError::FeatureDisabled("Semantic search not enabled".to_string()))?;
1130
1131    let cid: Cid = req
1132        .cid
1133        .parse()
1134        .map_err(|_| AppError::InvalidCid(req.cid.clone()))?;
1135
1136    semantic
1137        .add(&cid, &req.embedding)
1138        .map_err(|e| AppError::Semantic(format!("Failed to index: {}", e)))?;
1139
1140    info!("Indexed content {} with embedding", cid);
1141
1142    Ok(Json(SemanticIndexResponse { indexed: true }))
1143}
1144
1145/// Semantic search request
1146#[derive(Deserialize)]
1147struct SemanticSearchRequest {
1148    query: Vec<f32>,
1149    k: Option<usize>,
1150    filter: Option<QueryFilter>,
1151}
1152
1153/// Semantic search response
1154#[derive(Serialize)]
1155struct SemanticSearchResponse {
1156    results: Vec<SearchResultJson>,
1157}
1158
1159#[derive(Serialize)]
1160struct SearchResultJson {
1161    cid: String,
1162    score: f32,
1163}
1164
1165async fn api_semantic_search(
1166    State(state): State<GatewayState>,
1167    Json(req): Json<SemanticSearchRequest>,
1168) -> Result<Json<SemanticSearchResponse>, AppError> {
1169    let semantic = state
1170        .semantic
1171        .as_ref()
1172        .ok_or_else(|| AppError::FeatureDisabled("Semantic search not enabled".to_string()))?;
1173
1174    let k = req.k.unwrap_or(10);
1175
1176    let results = if let Some(filter) = req.filter {
1177        semantic
1178            .query_with_filter(&req.query, k, filter)
1179            .await
1180            .map_err(|e| AppError::Semantic(format!("Search failed: {}", e)))?
1181    } else {
1182        semantic
1183            .query(&req.query, k)
1184            .await
1185            .map_err(|e| AppError::Semantic(format!("Search failed: {}", e)))?
1186    };
1187
1188    let results_json: Vec<SearchResultJson> = results
1189        .into_iter()
1190        .map(|r| SearchResultJson {
1191            cid: r.cid.to_string(),
1192            score: r.score,
1193        })
1194        .collect();
1195
1196    Ok(Json(SemanticSearchResponse {
1197        results: results_json,
1198    }))
1199}
1200
1201/// Semantic stats response
1202#[derive(Serialize)]
1203struct SemanticStatsResponse {
1204    num_vectors: usize,
1205    dimension: usize,
1206    metric: String,
1207    cache_size: usize,
1208    cache_capacity: usize,
1209}
1210
1211async fn api_semantic_stats(
1212    State(state): State<GatewayState>,
1213) -> Result<Json<SemanticStatsResponse>, AppError> {
1214    let semantic = state
1215        .semantic
1216        .as_ref()
1217        .ok_or_else(|| AppError::FeatureDisabled("Semantic search not enabled".to_string()))?;
1218
1219    let router_stats = semantic.stats();
1220    let cache_stats = semantic.cache_stats();
1221
1222    let metric_str = match router_stats.metric {
1223        DistanceMetric::Cosine => "cosine",
1224        DistanceMetric::L2 => "l2",
1225        DistanceMetric::DotProduct => "dotproduct",
1226    };
1227
1228    Ok(Json(SemanticStatsResponse {
1229        num_vectors: router_stats.num_vectors,
1230        dimension: router_stats.dimension,
1231        metric: metric_str.to_string(),
1232        cache_size: cache_stats.size,
1233        cache_capacity: cache_stats.capacity,
1234    }))
1235}
1236
1237/// Semantic save request
1238#[derive(Deserialize)]
1239struct SemanticSaveRequest {
1240    path: String,
1241}
1242
1243/// Semantic save response
1244#[derive(Serialize)]
1245struct SemanticSaveResponse {
1246    success: bool,
1247    path: String,
1248}
1249
1250async fn api_semantic_save(
1251    State(state): State<GatewayState>,
1252    Json(req): Json<SemanticSaveRequest>,
1253) -> Result<Json<SemanticSaveResponse>, AppError> {
1254    let semantic = state
1255        .semantic
1256        .as_ref()
1257        .ok_or_else(|| AppError::FeatureDisabled("Semantic search not enabled".to_string()))?;
1258
1259    semantic
1260        .save_index(&req.path)
1261        .await
1262        .map_err(|e| AppError::Semantic(format!("Failed to save index: {}", e)))?;
1263
1264    info!("Saved semantic index to {}", req.path);
1265
1266    Ok(Json(SemanticSaveResponse {
1267        success: true,
1268        path: req.path,
1269    }))
1270}
1271
1272/// Semantic load request
1273#[derive(Deserialize)]
1274struct SemanticLoadRequest {
1275    path: String,
1276}
1277
1278/// Semantic load response
1279#[derive(Serialize)]
1280struct SemanticLoadResponse {
1281    success: bool,
1282    path: String,
1283}
1284
1285async fn api_semantic_load(
1286    State(state): State<GatewayState>,
1287    Json(req): Json<SemanticLoadRequest>,
1288) -> Result<Json<SemanticLoadResponse>, AppError> {
1289    let semantic = state
1290        .semantic
1291        .as_ref()
1292        .ok_or_else(|| AppError::FeatureDisabled("Semantic search not enabled".to_string()))?;
1293
1294    semantic
1295        .load_index(&req.path)
1296        .await
1297        .map_err(|e| AppError::Semantic(format!("Failed to load index: {}", e)))?;
1298
1299    info!("Loaded semantic index from {}", req.path);
1300
1301    Ok(Json(SemanticLoadResponse {
1302        success: true,
1303        path: req.path,
1304    }))
1305}
1306
1307// ============================================================================
1308// TensorLogic Handlers
1309// ============================================================================
1310
1311/// Logic store term request
1312#[derive(Deserialize)]
1313struct LogicStoreTermRequest {
1314    term: Term,
1315}
1316
1317/// Logic store response
1318#[derive(Serialize)]
1319struct LogicStoreResponse {
1320    cid: String,
1321}
1322
1323async fn api_logic_store_term(
1324    State(state): State<GatewayState>,
1325    Json(req): Json<LogicStoreTermRequest>,
1326) -> Result<Json<LogicStoreResponse>, AppError> {
1327    let tensorlogic = state
1328        .tensorlogic
1329        .as_ref()
1330        .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1331
1332    let cid = tensorlogic
1333        .store_term(&req.term)
1334        .await
1335        .map_err(|e| AppError::Logic(format!("Failed to store term: {}", e)))?;
1336
1337    info!("Stored term as {}", cid);
1338
1339    Ok(Json(LogicStoreResponse {
1340        cid: cid.to_string(),
1341    }))
1342}
1343
1344/// Logic get term response
1345#[derive(Serialize)]
1346struct LogicGetTermResponse {
1347    term: Term,
1348}
1349
1350async fn api_logic_get_term(
1351    State(state): State<GatewayState>,
1352    Path(cid_str): Path<String>,
1353) -> Result<Json<LogicGetTermResponse>, AppError> {
1354    let tensorlogic = state
1355        .tensorlogic
1356        .as_ref()
1357        .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1358
1359    let cid: Cid = cid_str
1360        .parse()
1361        .map_err(|_| AppError::InvalidCid(cid_str.clone()))?;
1362
1363    let term = tensorlogic
1364        .get_term(&cid)
1365        .await
1366        .map_err(|e| AppError::Logic(format!("Failed to get term: {}", e)))?
1367        .ok_or_else(|| AppError::NotFound(format!("Term not found: {}", cid_str)))?;
1368
1369    Ok(Json(LogicGetTermResponse { term }))
1370}
1371
1372/// Logic store predicate request
1373#[derive(Deserialize)]
1374struct LogicStorePredicateRequest {
1375    predicate: Predicate,
1376}
1377
1378async fn api_logic_store_predicate(
1379    State(state): State<GatewayState>,
1380    Json(req): Json<LogicStorePredicateRequest>,
1381) -> Result<Json<LogicStoreResponse>, AppError> {
1382    let tensorlogic = state
1383        .tensorlogic
1384        .as_ref()
1385        .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1386
1387    let cid = tensorlogic
1388        .store_predicate(&req.predicate)
1389        .await
1390        .map_err(|e| AppError::Logic(format!("Failed to store predicate: {}", e)))?;
1391
1392    info!("Stored predicate as {}", cid);
1393
1394    Ok(Json(LogicStoreResponse {
1395        cid: cid.to_string(),
1396    }))
1397}
1398
1399/// Logic store rule request
1400#[derive(Deserialize)]
1401struct LogicStoreRuleRequest {
1402    rule: Rule,
1403}
1404
1405async fn api_logic_store_rule(
1406    State(state): State<GatewayState>,
1407    Json(req): Json<LogicStoreRuleRequest>,
1408) -> Result<Json<LogicStoreResponse>, AppError> {
1409    let tensorlogic = state
1410        .tensorlogic
1411        .as_ref()
1412        .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1413
1414    let cid = tensorlogic
1415        .store_rule(&req.rule)
1416        .await
1417        .map_err(|e| AppError::Logic(format!("Failed to store rule: {}", e)))?;
1418
1419    info!("Stored rule as {}", cid);
1420
1421    Ok(Json(LogicStoreResponse {
1422        cid: cid.to_string(),
1423    }))
1424}
1425
1426/// Logic stats response
1427#[derive(Serialize)]
1428struct LogicStatsResponse {
1429    enabled: bool,
1430}
1431
1432async fn api_logic_stats(
1433    State(state): State<GatewayState>,
1434) -> Result<Json<LogicStatsResponse>, AppError> {
1435    let enabled = state.tensorlogic.is_some();
1436
1437    Ok(Json(LogicStatsResponse { enabled }))
1438}
1439
1440/// Add fact request
1441#[derive(Deserialize)]
1442struct AddFactRequest {
1443    fact: Predicate,
1444}
1445
1446/// Add fact response
1447#[derive(Serialize)]
1448struct AddFactResponse {
1449    success: bool,
1450}
1451
1452async fn api_logic_add_fact(
1453    State(state): State<GatewayState>,
1454    Json(req): Json<AddFactRequest>,
1455) -> Result<Json<AddFactResponse>, AppError> {
1456    let tensorlogic = state
1457        .tensorlogic
1458        .as_ref()
1459        .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1460
1461    tensorlogic
1462        .add_fact(req.fact)
1463        .map_err(|e| AppError::Logic(format!("Failed to add fact: {}", e)))?;
1464
1465    Ok(Json(AddFactResponse { success: true }))
1466}
1467
1468/// Add rule request
1469#[derive(Deserialize)]
1470struct AddRuleRequest {
1471    rule: Rule,
1472}
1473
1474/// Add rule response
1475#[derive(Serialize)]
1476struct AddRuleResponse {
1477    success: bool,
1478}
1479
1480async fn api_logic_add_rule(
1481    State(state): State<GatewayState>,
1482    Json(req): Json<AddRuleRequest>,
1483) -> Result<Json<AddRuleResponse>, AppError> {
1484    let tensorlogic = state
1485        .tensorlogic
1486        .as_ref()
1487        .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1488
1489    tensorlogic
1490        .add_rule(req.rule)
1491        .map_err(|e| AppError::Logic(format!("Failed to add rule: {}", e)))?;
1492
1493    Ok(Json(AddRuleResponse { success: true }))
1494}
1495
1496/// Infer request
1497#[derive(Deserialize)]
1498struct InferRequest {
1499    goal: Predicate,
1500}
1501
1502/// Infer response
1503#[derive(Serialize)]
1504struct InferResponse {
1505    solutions: Vec<Substitution>,
1506}
1507
1508async fn api_logic_infer(
1509    State(state): State<GatewayState>,
1510    Json(req): Json<InferRequest>,
1511) -> Result<Json<InferResponse>, AppError> {
1512    let tensorlogic = state
1513        .tensorlogic
1514        .as_ref()
1515        .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1516
1517    let solutions = tensorlogic
1518        .infer(&req.goal)
1519        .map_err(|e| AppError::Logic(format!("Inference failed: {}", e)))?;
1520
1521    Ok(Json(InferResponse { solutions }))
1522}
1523
1524/// Prove request
1525#[derive(Deserialize)]
1526struct ProveRequest {
1527    goal: Predicate,
1528}
1529
1530/// Prove response
1531#[derive(Serialize)]
1532struct ProveResponse {
1533    proof: Option<Proof>,
1534    cid: Option<String>,
1535}
1536
1537/// Verify request
1538#[derive(Deserialize)]
1539struct VerifyRequest {
1540    proof: Proof,
1541}
1542
1543/// Verify response
1544#[derive(Serialize)]
1545struct VerifyResponse {
1546    valid: bool,
1547}
1548
1549async fn api_logic_prove(
1550    State(state): State<GatewayState>,
1551    Json(req): Json<ProveRequest>,
1552) -> Result<Json<ProveResponse>, AppError> {
1553    let tensorlogic = state
1554        .tensorlogic
1555        .as_ref()
1556        .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1557
1558    let proof = tensorlogic
1559        .prove(&req.goal)
1560        .map_err(|e| AppError::Logic(format!("Proof generation failed: {}", e)))?;
1561
1562    if let Some(ref p) = proof {
1563        let cid = tensorlogic
1564            .store_proof(p)
1565            .await
1566            .map_err(|e| AppError::Logic(format!("Failed to store proof: {}", e)))?;
1567
1568        Ok(Json(ProveResponse {
1569            proof,
1570            cid: Some(cid.to_string()),
1571        }))
1572    } else {
1573        Ok(Json(ProveResponse {
1574            proof: None,
1575            cid: None,
1576        }))
1577    }
1578}
1579
1580async fn api_logic_verify(
1581    State(state): State<GatewayState>,
1582    Json(req): Json<VerifyRequest>,
1583) -> Result<Json<VerifyResponse>, AppError> {
1584    let tensorlogic = state
1585        .tensorlogic
1586        .as_ref()
1587        .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1588
1589    let valid = tensorlogic
1590        .verify_proof(&req.proof)
1591        .map_err(|e| AppError::Logic(format!("Proof verification failed: {}", e)))?;
1592
1593    Ok(Json(VerifyResponse { valid }))
1594}
1595
1596/// Get proof response
1597#[derive(Serialize)]
1598struct GetProofResponse {
1599    proof: Proof,
1600}
1601
1602async fn api_logic_get_proof(
1603    State(state): State<GatewayState>,
1604    Path(cid_str): Path<String>,
1605) -> Result<Json<GetProofResponse>, AppError> {
1606    let tensorlogic = state
1607        .tensorlogic
1608        .as_ref()
1609        .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1610
1611    let cid: Cid = cid_str
1612        .parse()
1613        .map_err(|e| AppError::InvalidCid(format!("Invalid CID: {}", e)))?;
1614
1615    let proof = tensorlogic
1616        .get_proof(&cid)
1617        .await
1618        .map_err(|e| AppError::Logic(format!("Failed to get proof: {}", e)))?
1619        .ok_or_else(|| AppError::NotFound(format!("Proof not found: {}", cid)))?;
1620
1621    Ok(Json(GetProofResponse { proof }))
1622}
1623
1624/// KB stats response
1625#[derive(Serialize)]
1626struct KbStatsResponse {
1627    num_facts: usize,
1628    num_rules: usize,
1629}
1630
1631async fn api_logic_kb_stats(
1632    State(state): State<GatewayState>,
1633) -> Result<Json<KbStatsResponse>, AppError> {
1634    let tensorlogic = state
1635        .tensorlogic
1636        .as_ref()
1637        .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1638
1639    let kb_stats = tensorlogic.kb_stats();
1640
1641    Ok(Json(KbStatsResponse {
1642        num_facts: kb_stats.num_facts,
1643        num_rules: kb_stats.num_rules,
1644    }))
1645}
1646
1647/// KB save request
1648#[derive(Deserialize)]
1649struct KbSaveRequest {
1650    path: String,
1651}
1652
1653/// KB save response
1654#[derive(Serialize)]
1655struct KbSaveResponse {
1656    success: bool,
1657    path: String,
1658}
1659
1660async fn api_logic_kb_save(
1661    State(state): State<GatewayState>,
1662    Json(req): Json<KbSaveRequest>,
1663) -> Result<Json<KbSaveResponse>, AppError> {
1664    let tensorlogic = state
1665        .tensorlogic
1666        .as_ref()
1667        .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1668
1669    tensorlogic
1670        .save_kb(&req.path)
1671        .await
1672        .map_err(|e| AppError::Logic(format!("Failed to save knowledge base: {}", e)))?;
1673
1674    info!("Saved knowledge base to {}", req.path);
1675
1676    Ok(Json(KbSaveResponse {
1677        success: true,
1678        path: req.path,
1679    }))
1680}
1681
1682/// KB load request
1683#[derive(Deserialize)]
1684struct KbLoadRequest {
1685    path: String,
1686}
1687
1688/// KB load response
1689#[derive(Serialize)]
1690struct KbLoadResponse {
1691    success: bool,
1692    path: String,
1693}
1694
1695async fn api_logic_kb_load(
1696    State(state): State<GatewayState>,
1697    Json(req): Json<KbLoadRequest>,
1698) -> Result<Json<KbLoadResponse>, AppError> {
1699    let tensorlogic = state
1700        .tensorlogic
1701        .as_ref()
1702        .ok_or_else(|| AppError::FeatureDisabled("TensorLogic not enabled".to_string()))?;
1703
1704    tensorlogic
1705        .load_kb(&req.path)
1706        .await
1707        .map_err(|e| AppError::Logic(format!("Failed to load knowledge base: {}", e)))?;
1708
1709    info!("Loaded knowledge base from {}", req.path);
1710
1711    Ok(Json(KbLoadResponse {
1712        success: true,
1713        path: req.path,
1714    }))
1715}
1716
1717// ============================================================================
1718// Network Handlers
1719// ============================================================================
1720
1721/// Network ID response
1722#[derive(Serialize)]
1723struct NetworkIdResponse {
1724    #[serde(rename = "ID")]
1725    id: String,
1726    #[serde(rename = "Addresses")]
1727    addresses: Vec<String>,
1728}
1729
1730async fn api_network_id(
1731    State(state): State<GatewayState>,
1732) -> Result<Json<NetworkIdResponse>, AppError> {
1733    let network = state
1734        .network
1735        .as_ref()
1736        .ok_or_else(|| AppError::FeatureDisabled("Network not enabled".to_string()))?;
1737
1738    let (peer_id, listeners) = {
1739        let network = network.lock().await;
1740        (network.peer_id().to_string(), network.listeners())
1741    };
1742
1743    let addresses = listeners
1744        .iter()
1745        .map(|addr| format!("{}/p2p/{}", addr, peer_id))
1746        .collect();
1747
1748    Ok(Json(NetworkIdResponse {
1749        id: peer_id,
1750        addresses,
1751    }))
1752}
1753
1754/// Swarm peers response
1755#[derive(Serialize)]
1756struct SwarmPeersResponse {
1757    #[serde(rename = "Peers")]
1758    peers: Vec<PeerEntry>,
1759}
1760
1761#[derive(Serialize)]
1762struct PeerEntry {
1763    #[serde(rename = "Peer")]
1764    peer: String,
1765}
1766
1767async fn api_swarm_peers(
1768    State(state): State<GatewayState>,
1769) -> Result<Json<SwarmPeersResponse>, AppError> {
1770    let network = state
1771        .network
1772        .as_ref()
1773        .ok_or_else(|| AppError::FeatureDisabled("Network not enabled".to_string()))?;
1774
1775    let peers = {
1776        let network = network.lock().await;
1777        network.connected_peers()
1778    };
1779
1780    let peer_entries: Vec<PeerEntry> = peers
1781        .into_iter()
1782        .map(|p| PeerEntry {
1783            peer: p.to_string(),
1784        })
1785        .collect();
1786
1787    Ok(Json(SwarmPeersResponse {
1788        peers: peer_entries,
1789    }))
1790}
1791
1792/// Swarm connect request
1793#[derive(Deserialize)]
1794struct SwarmConnectRequest {
1795    arg: String,
1796}
1797
1798/// Swarm connect response
1799#[derive(Serialize)]
1800struct SwarmConnectResponse {
1801    #[serde(rename = "Strings")]
1802    strings: Vec<String>,
1803}
1804
1805async fn api_swarm_connect(
1806    State(state): State<GatewayState>,
1807    Json(req): Json<SwarmConnectRequest>,
1808) -> Result<Json<SwarmConnectResponse>, AppError> {
1809    let network = state
1810        .network
1811        .as_ref()
1812        .ok_or_else(|| AppError::FeatureDisabled("Network not enabled".to_string()))?;
1813
1814    let addr: ipfrs_network::libp2p::Multiaddr = req
1815        .arg
1816        .parse()
1817        .map_err(|e| AppError::Network(format!("Invalid multiaddr: {}", e)))?;
1818
1819    {
1820        let mut network = network.lock().await;
1821        network
1822            .connect(addr.clone())
1823            .await
1824            .map_err(|e| AppError::Network(format!("Connect failed: {}", e)))?;
1825    }
1826
1827    info!("Connected to peer: {}", req.arg);
1828
1829    Ok(Json(SwarmConnectResponse {
1830        strings: vec![format!("connect {} success", req.arg)],
1831    }))
1832}
1833
1834/// Swarm disconnect request
1835#[derive(Deserialize)]
1836struct SwarmDisconnectRequest {
1837    arg: String,
1838}
1839
1840/// Swarm disconnect response
1841#[derive(Serialize)]
1842struct SwarmDisconnectResponse {
1843    #[serde(rename = "Strings")]
1844    strings: Vec<String>,
1845}
1846
1847async fn api_swarm_disconnect(
1848    State(state): State<GatewayState>,
1849    Json(req): Json<SwarmDisconnectRequest>,
1850) -> Result<Json<SwarmDisconnectResponse>, AppError> {
1851    let network = state
1852        .network
1853        .as_ref()
1854        .ok_or_else(|| AppError::FeatureDisabled("Network not enabled".to_string()))?;
1855
1856    let peer_id: ipfrs_network::libp2p::PeerId = req
1857        .arg
1858        .parse()
1859        .map_err(|e| AppError::Network(format!("Invalid peer ID: {}", e)))?;
1860
1861    {
1862        let mut network = network.lock().await;
1863        network
1864            .disconnect(peer_id)
1865            .await
1866            .map_err(|e| AppError::Network(format!("Disconnect failed: {}", e)))?;
1867    }
1868
1869    info!("Disconnected from peer: {}", req.arg);
1870
1871    Ok(Json(SwarmDisconnectResponse {
1872        strings: vec![format!("disconnect {} success", req.arg)],
1873    }))
1874}
1875
1876/// DHT findprovs request
1877#[derive(Deserialize)]
1878struct DhtFindprovsRequest {
1879    arg: String,
1880}
1881
1882/// DHT findprovs response
1883#[derive(Serialize)]
1884struct DhtFindprovsResponse {
1885    #[serde(rename = "Responses")]
1886    responses: Vec<DhtProviderEntry>,
1887}
1888
1889#[derive(Serialize)]
1890struct DhtProviderEntry {
1891    #[serde(rename = "ID")]
1892    id: String,
1893}
1894
1895async fn api_dht_findprovs(
1896    State(state): State<GatewayState>,
1897    Json(req): Json<DhtFindprovsRequest>,
1898) -> Result<Json<DhtFindprovsResponse>, AppError> {
1899    let network = state
1900        .network
1901        .as_ref()
1902        .ok_or_else(|| AppError::FeatureDisabled("Network not enabled".to_string()))?;
1903
1904    let cid: Cid = req
1905        .arg
1906        .parse()
1907        .map_err(|_| AppError::InvalidCid(req.arg.clone()))?;
1908
1909    {
1910        let mut network = network.lock().await;
1911        network
1912            .find_providers(&cid)
1913            .await
1914            .map_err(|e| AppError::Network(format!("Find providers failed: {}", e)))?;
1915    }
1916
1917    info!("Finding providers for: {}", req.arg);
1918
1919    // Note: In a real implementation, we would wait for the query to complete
1920    // and return the actual providers. For now, return empty list as the query
1921    // is asynchronous and results come via events.
1922    Ok(Json(DhtFindprovsResponse { responses: vec![] }))
1923}
1924
1925/// DHT provide request
1926#[derive(Deserialize)]
1927struct DhtProvideRequest {
1928    arg: String,
1929}
1930
1931/// DHT provide response
1932#[derive(Serialize)]
1933struct DhtProvideResponse {
1934    #[serde(rename = "ID")]
1935    id: String,
1936}
1937
1938async fn api_dht_provide(
1939    State(state): State<GatewayState>,
1940    Json(req): Json<DhtProvideRequest>,
1941) -> Result<Json<DhtProvideResponse>, AppError> {
1942    let network = state
1943        .network
1944        .as_ref()
1945        .ok_or_else(|| AppError::FeatureDisabled("Network not enabled".to_string()))?;
1946
1947    let cid: Cid = req
1948        .arg
1949        .parse()
1950        .map_err(|_| AppError::InvalidCid(req.arg.clone()))?;
1951
1952    {
1953        let mut network = network.lock().await;
1954        network
1955            .provide(&cid)
1956            .await
1957            .map_err(|e| AppError::Network(format!("Provide failed: {}", e)))?;
1958    }
1959
1960    info!("Announcing content to DHT: {}", req.arg);
1961
1962    Ok(Json(DhtProvideResponse { id: req.arg }))
1963}
1964
1965// ============================================================================
1966// GraphQL Handlers
1967// ============================================================================
1968
1969/// GraphQL query handler
1970async fn graphql_handler(
1971    State(state): State<GatewayState>,
1972    req: GraphQLRequest,
1973) -> Result<GraphQLResponse, AppError> {
1974    let schema = state
1975        .graphql_schema
1976        .as_ref()
1977        .ok_or_else(|| AppError::FeatureDisabled("GraphQL not enabled".to_string()))?;
1978
1979    Ok(schema.execute(req.into_inner()).await.into())
1980}
1981
1982/// GraphQL playground handler
1983async fn graphql_playground() -> impl IntoResponse {
1984    Html(playground_source(GraphQLPlaygroundConfig::new("/graphql")))
1985}
1986
1987// ============================================================================
1988// Error Handling
1989// ============================================================================
1990
1991/// Application error types
1992#[derive(Debug)]
1993enum AppError {
1994    InvalidCid(String),
1995    BlockNotFound(String),
1996    NotFound(String),
1997    Upload(String),
1998    Storage(ipfrs_core::Error),
1999    FeatureDisabled(String),
2000    Semantic(String),
2001    Logic(String),
2002    Network(String),
2003}
2004
2005impl From<ipfrs_core::Error> for AppError {
2006    fn from(err: ipfrs_core::Error) -> Self {
2007        AppError::Storage(err)
2008    }
2009}
2010
2011impl IntoResponse for AppError {
2012    fn into_response(self) -> Response {
2013        let (status, message) = match self {
2014            AppError::InvalidCid(cid) => (StatusCode::BAD_REQUEST, format!("Invalid CID: {}", cid)),
2015            AppError::BlockNotFound(cid) => {
2016                (StatusCode::NOT_FOUND, format!("Block not found: {}", cid))
2017            }
2018            AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
2019            AppError::Upload(msg) => {
2020                error!("Upload error: {}", msg);
2021                (StatusCode::BAD_REQUEST, format!("Upload error: {}", msg))
2022            }
2023            AppError::Storage(err) => {
2024                error!("Storage error: {}", err);
2025                (
2026                    StatusCode::INTERNAL_SERVER_ERROR,
2027                    format!("Storage error: {}", err),
2028                )
2029            }
2030            AppError::FeatureDisabled(msg) => (
2031                StatusCode::SERVICE_UNAVAILABLE,
2032                format!("Feature not available: {}", msg),
2033            ),
2034            AppError::Semantic(msg) => {
2035                error!("Semantic error: {}", msg);
2036                (
2037                    StatusCode::INTERNAL_SERVER_ERROR,
2038                    format!("Semantic error: {}", msg),
2039                )
2040            }
2041            AppError::Logic(msg) => {
2042                error!("Logic error: {}", msg);
2043                (
2044                    StatusCode::INTERNAL_SERVER_ERROR,
2045                    format!("Logic error: {}", msg),
2046                )
2047            }
2048            AppError::Network(msg) => {
2049                error!("Network error: {}", msg);
2050                (
2051                    StatusCode::INTERNAL_SERVER_ERROR,
2052                    format!("Network error: {}", msg),
2053                )
2054            }
2055        };
2056
2057        (status, message).into_response()
2058    }
2059}
2060
2061#[cfg(test)]
2062mod tests {
2063    use super::*;
2064
2065    #[test]
2066    fn test_parse_single_range() {
2067        // Standard range
2068        assert_eq!(parse_range("bytes=0-100", 1000), Some((0, 101)));
2069
2070        // Open-ended range (to end)
2071        assert_eq!(parse_range("bytes=500-", 1000), Some((500, 1000)));
2072
2073        // Invalid: start >= size
2074        assert_eq!(parse_range("bytes=1000-1100", 1000), None);
2075
2076        // Invalid: start > end
2077        assert_eq!(parse_range("bytes=500-100", 1000), None);
2078
2079        // Invalid format
2080        assert_eq!(parse_range("bytes=abc-100", 1000), None);
2081        assert_eq!(parse_range("invalid", 1000), None);
2082    }
2083
2084    #[test]
2085    fn test_parse_multi_range() {
2086        // Single range via multi-range parser
2087        let ranges = parse_multi_range("bytes=0-100", 1000);
2088        assert_eq!(ranges, Some(vec![(0, 101)]));
2089
2090        // Multiple ranges
2091        let ranges = parse_multi_range("bytes=0-100,200-300", 1000);
2092        assert_eq!(ranges, Some(vec![(0, 101), (200, 301)]));
2093
2094        // Multiple ranges with spaces
2095        let ranges = parse_multi_range("bytes=0-100, 200-300, 500-600", 1000);
2096        assert_eq!(ranges, Some(vec![(0, 101), (200, 301), (500, 601)]));
2097
2098        // Suffix range (last N bytes)
2099        let ranges = parse_multi_range("bytes=-500", 1000);
2100        assert_eq!(ranges, Some(vec![(500, 1000)]));
2101
2102        // Invalid range
2103        assert_eq!(parse_multi_range("bytes=1000-1100", 1000), None);
2104
2105        // Invalid format
2106        assert_eq!(parse_multi_range("invalid", 1000), None);
2107    }
2108
2109    #[test]
2110    fn test_merge_ranges() {
2111        // Non-overlapping ranges (should not merge)
2112        let ranges = vec![(0, 100), (200, 300)];
2113        assert_eq!(merge_ranges(ranges), vec![(0, 100), (200, 300)]);
2114
2115        // Overlapping ranges (should merge)
2116        let ranges = vec![(0, 150), (100, 200)];
2117        assert_eq!(merge_ranges(ranges), vec![(0, 200)]);
2118
2119        // Adjacent ranges (should merge)
2120        let ranges = vec![(0, 100), (100, 200)];
2121        assert_eq!(merge_ranges(ranges), vec![(0, 200)]);
2122
2123        // Out of order ranges (should sort and merge)
2124        let ranges = vec![(200, 300), (0, 100), (50, 150)];
2125        assert_eq!(merge_ranges(ranges), vec![(0, 150), (200, 300)]);
2126
2127        // Single range (no change)
2128        let ranges = vec![(50, 100)];
2129        assert_eq!(merge_ranges(ranges), vec![(50, 100)]);
2130
2131        // Empty (no change)
2132        let ranges: Vec<(usize, usize)> = vec![];
2133        assert_eq!(merge_ranges(ranges), vec![]);
2134    }
2135
2136    #[test]
2137    fn test_build_multipart_response() {
2138        let data = b"Hello, World! This is test data for multi-range requests.";
2139        let ranges = vec![(0, 5), (7, 12)];
2140        let total_size = data.len();
2141        let config = CacheConfig::default();
2142
2143        let response = build_multipart_response(data, &ranges, total_size, "QmTest123", &config);
2144
2145        assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
2146
2147        let content_type = response
2148            .headers()
2149            .get(header::CONTENT_TYPE)
2150            .unwrap()
2151            .to_str()
2152            .unwrap();
2153        assert!(content_type.starts_with("multipart/byteranges"));
2154        assert!(content_type.contains("boundary="));
2155
2156        // Check caching headers are present
2157        assert!(response.headers().contains_key(header::ETAG));
2158        assert!(response.headers().contains_key(header::CACHE_CONTROL));
2159    }
2160
2161    #[test]
2162    fn test_config_presets() {
2163        // Test production preset
2164        let prod = GatewayConfig::production();
2165        assert_eq!(prod.listen_addr, "0.0.0.0:8080");
2166        assert!(prod.compression_config.enable_gzip);
2167        assert!(prod.compression_config.enable_brotli);
2168        assert!(prod.compression_config.enable_deflate);
2169
2170        // Test development preset
2171        let dev = GatewayConfig::development();
2172        assert_eq!(dev.listen_addr, "127.0.0.1:8080");
2173        assert!(dev.compression_config.enable_gzip);
2174        assert!(!dev.compression_config.enable_brotli);
2175
2176        // Test testing preset
2177        let test = GatewayConfig::testing();
2178        assert_eq!(test.listen_addr, "127.0.0.1:0");
2179        assert!(!test.compression_config.enable_gzip);
2180        assert!(!test.compression_config.enable_brotli);
2181    }
2182
2183    #[test]
2184    fn test_config_builders() {
2185        let config = GatewayConfig::default()
2186            .with_listen_addr("0.0.0.0:9090")
2187            .with_storage_path("/custom/path")
2188            .with_cache_mb(200)
2189            .with_full_compression();
2190
2191        assert_eq!(config.listen_addr, "0.0.0.0:9090");
2192        assert!(config.compression_config.enable_gzip);
2193        assert!(config.compression_config.enable_brotli);
2194        assert!(config.compression_config.enable_deflate);
2195    }
2196
2197    #[test]
2198    fn test_config_validation() {
2199        // Valid config should pass
2200        let valid_config = GatewayConfig::default();
2201        assert!(valid_config.validate().is_ok());
2202
2203        // Invalid address should fail
2204        let invalid_addr = GatewayConfig {
2205            listen_addr: "invalid-address".to_string(),
2206            ..Default::default()
2207        };
2208        assert!(invalid_addr.validate().is_err());
2209
2210        // Empty address should fail
2211        let empty_addr = GatewayConfig {
2212            listen_addr: "".to_string(),
2213            ..Default::default()
2214        };
2215        assert!(empty_addr.validate().is_err());
2216    }
2217
2218    #[test]
2219    fn test_compression_helpers() {
2220        let config_with = GatewayConfig::default().with_full_compression();
2221        assert!(config_with.compression_config.enable_gzip);
2222        assert!(config_with.compression_config.enable_brotli);
2223        assert!(config_with.compression_config.enable_deflate);
2224
2225        let config_without = GatewayConfig::default().without_compression();
2226        assert!(!config_without.compression_config.enable_gzip);
2227        assert!(!config_without.compression_config.enable_brotli);
2228        assert!(!config_without.compression_config.enable_deflate);
2229    }
2230}