1use axum::{
6 http::StatusCode,
7 response::{IntoResponse, Response},
8 routing::{get, post},
9 Router,
10};
11use ipfrs_core::Result as CoreResult;
12use ipfrs_semantic::{RouterConfig, SemanticRouter};
13use ipfrs_storage::{BlockStoreConfig, SledBlockStore};
14use ipfrs_tensorlogic::TensorLogicStore;
15use std::sync::Arc;
16use tower_http::trace::TraceLayer;
17use tracing::{error, info};
18
19use crate::auth::AuthState;
20use crate::auth_handlers;
21use crate::graphql::{create_schema, IpfrsSchema};
22use crate::streaming;
23use crate::tensor;
24use crate::tls::TlsConfig;
25
26#[derive(Clone)]
28pub struct GatewayState {
29 pub(crate) store: Arc<SledBlockStore>,
30 semantic: Option<Arc<SemanticRouter>>,
31 tensorlogic: Option<Arc<TensorLogicStore<SledBlockStore>>>,
32 network: Option<Arc<tokio::sync::Mutex<ipfrs_network::NetworkNode>>>,
33 graphql_schema: Option<IpfrsSchema>,
34 pub(crate) auth: Option<AuthState>,
35}
36
37impl GatewayState {
38 pub fn new(config: BlockStoreConfig) -> CoreResult<Self> {
40 let store = SledBlockStore::new(config)?;
41 Ok(Self {
42 store: Arc::new(store),
43 semantic: None,
44 tensorlogic: None,
45 network: None,
46 graphql_schema: None,
47 auth: None,
48 })
49 }
50
51 pub fn with_auth(
53 mut self,
54 secret: &[u8],
55 default_admin_password: Option<&str>,
56 ) -> CoreResult<Self> {
57 let auth_state = if let Some(password) = default_admin_password {
58 AuthState::with_default_admin(secret, password).map_err(|e| {
59 ipfrs_core::Error::Internal(format!("Failed to create auth state: {}", e))
60 })?
61 } else {
62 AuthState::new(secret)
63 };
64 self.auth = Some(auth_state);
65 Ok(self)
66 }
67
68 pub fn with_semantic(mut self, config: RouterConfig) -> CoreResult<Self> {
70 let semantic = SemanticRouter::new(config).map_err(|e| {
71 ipfrs_core::Error::Internal(format!("Failed to create semantic router: {}", e))
72 })?;
73 self.semantic = Some(Arc::new(semantic));
74 Ok(self)
75 }
76
77 pub fn with_tensorlogic(mut self) -> CoreResult<Self> {
79 let tensorlogic = TensorLogicStore::new(Arc::clone(&self.store))?;
80 self.tensorlogic = Some(Arc::new(tensorlogic));
81 Ok(self)
82 }
83
84 pub fn with_network(mut self, network: ipfrs_network::NetworkNode) -> Self {
86 self.network = Some(Arc::new(tokio::sync::Mutex::new(network)));
87 self
88 }
89
90 pub fn with_graphql(mut self) -> Self {
92 let schema = create_schema(
93 Arc::clone(&self.store),
94 self.semantic.clone(),
95 self.tensorlogic.clone(),
96 );
97 self.graphql_schema = Some(schema);
98 self
99 }
100}
101
102#[derive(Debug, Clone)]
104pub struct GatewayConfig {
105 pub listen_addr: String,
107 pub storage_config: BlockStoreConfig,
109 pub tls_config: Option<TlsConfig>,
111 pub compression_config: crate::middleware::CompressionConfig,
113}
114
115impl Default for GatewayConfig {
116 fn default() -> Self {
117 Self {
118 listen_addr: "127.0.0.1:8080".to_string(),
119 storage_config: BlockStoreConfig::default(),
120 tls_config: None,
121 compression_config: crate::middleware::CompressionConfig::default(),
122 }
123 }
124}
125
126impl GatewayConfig {
127 pub fn production() -> Self {
135 Self {
136 listen_addr: "0.0.0.0:8080".to_string(),
137 storage_config: BlockStoreConfig::default()
138 .with_path("./ipfrs_data".into())
139 .with_cache_mb(500),
140 tls_config: None,
141 compression_config: crate::middleware::CompressionConfig {
142 enable_gzip: true,
143 level: crate::middleware::CompressionLevel::Best,
144 min_size: 512,
145 },
146 }
147 }
148
149 pub fn development() -> Self {
157 Self {
158 listen_addr: "127.0.0.1:8080".to_string(),
159 storage_config: BlockStoreConfig::default()
160 .with_path("./dev_data".into())
161 .with_cache_mb(50),
162 tls_config: None,
163 compression_config: crate::middleware::CompressionConfig {
164 enable_gzip: true,
165 level: crate::middleware::CompressionLevel::Fastest,
166 min_size: 2048,
167 },
168 }
169 }
170
171 pub fn testing() -> Self {
179 Self {
180 listen_addr: "127.0.0.1:0".to_string(),
181 storage_config: BlockStoreConfig::default()
182 .with_path(std::env::temp_dir().join("ipfrs_test"))
183 .with_cache_mb(10),
184 tls_config: None,
185 compression_config: crate::middleware::CompressionConfig {
186 enable_gzip: false,
187 level: crate::middleware::CompressionLevel::Fastest,
188 min_size: 1048576, },
190 }
191 }
192
193 pub fn with_listen_addr(mut self, addr: impl Into<String>) -> Self {
195 self.listen_addr = addr.into();
196 self
197 }
198
199 pub fn with_storage_path(mut self, path: impl Into<String>) -> Self {
201 self.storage_config = self.storage_config.with_path(path.into().into());
202 self
203 }
204
205 pub fn with_cache_mb(mut self, size_mb: usize) -> Self {
207 self.storage_config = self.storage_config.with_cache_mb(size_mb);
208 self
209 }
210
211 pub fn with_tls(mut self, tls_config: TlsConfig) -> Self {
213 self.tls_config = Some(tls_config);
214 self
215 }
216
217 pub fn with_compression_level(mut self, level: crate::middleware::CompressionLevel) -> Self {
219 self.compression_config.level = level;
220 self
221 }
222
223 pub fn with_full_compression(mut self) -> Self {
225 self.compression_config.enable_gzip = true;
226 self
227 }
228
229 pub fn without_compression(mut self) -> Self {
231 self.compression_config.enable_gzip = false;
232 self
233 }
234
235 pub fn validate(&self) -> CoreResult<()> {
239 if self.listen_addr.is_empty() {
241 return Err(ipfrs_core::Error::Internal(
242 "Listen address cannot be empty".to_string(),
243 ));
244 }
245
246 self.listen_addr
248 .parse::<std::net::SocketAddr>()
249 .map_err(|e| ipfrs_core::Error::Internal(format!("Invalid listen address: {}", e)))?;
250
251 if self.storage_config.path.as_os_str().is_empty() {
253 return Err(ipfrs_core::Error::Internal(
254 "Storage path cannot be empty".to_string(),
255 ));
256 }
257
258 if self.compression_config.min_size == 0 {
260 return Err(ipfrs_core::Error::Internal(
261 "Compression min_size must be greater than 0".to_string(),
262 ));
263 }
264
265 Ok(())
266 }
267}
268
269pub struct Gateway {
271 config: GatewayConfig,
272 state: GatewayState,
273}
274
275impl Gateway {
276 pub fn new(config: GatewayConfig) -> CoreResult<Self> {
278 let state = GatewayState::new(config.storage_config.clone())?;
279 Ok(Self { config, state })
280 }
281
282 fn router(&self) -> Router {
284 let mut router = Router::new()
285 .route("/health", get(health_check))
287 .route("/metrics", get(metrics_endpoint))
289 .route("/ipfs/:cid", get(get_content))
291 .route("/api/v0/auth/login", post(auth_handlers::login_handler))
293 .route(
294 "/api/v0/auth/register",
295 post(auth_handlers::register_handler),
296 )
297 .route("/graphql", post(graphql_handler))
299 .route("/graphql", get(graphql_playground))
300 .route("/api/v0/version", get(api_version))
302 .route("/api/v0/add", post(api_add))
303 .route("/api/v0/block/get", post(api_block_get))
304 .route("/api/v0/block/put", post(api_block_put))
305 .route("/api/v0/block/stat", post(api_block_stat))
306 .route("/api/v0/cat", post(api_cat))
307 .route("/api/v0/dag/get", post(api_dag_get))
308 .route("/api/v0/dag/put", post(api_dag_put))
309 .route("/api/v0/dag/resolve", post(api_dag_resolve))
310 .route("/api/v0/semantic/index", post(api_semantic_index))
312 .route("/api/v0/semantic/search", post(api_semantic_search))
313 .route("/api/v0/semantic/stats", get(api_semantic_stats))
314 .route("/api/v0/semantic/save", post(api_semantic_save))
315 .route("/api/v0/semantic/load", post(api_semantic_load))
316 .route("/api/v0/logic/term", post(api_logic_store_term))
318 .route("/api/v0/logic/term/:cid", get(api_logic_get_term))
319 .route("/api/v0/logic/predicate", post(api_logic_store_predicate))
320 .route("/api/v0/logic/rule", post(api_logic_store_rule))
321 .route("/api/v0/logic/stats", get(api_logic_stats))
322 .route("/api/v0/logic/fact", post(api_logic_add_fact))
323 .route("/api/v0/logic/rule/add", post(api_logic_add_rule))
324 .route("/api/v0/logic/infer", post(api_logic_infer))
325 .route("/api/v0/logic/prove", post(api_logic_prove))
326 .route("/api/v0/logic/verify", post(api_logic_verify))
327 .route("/api/v0/logic/proof/:cid", get(api_logic_get_proof))
328 .route("/api/v0/logic/kb/stats", get(api_logic_kb_stats))
329 .route("/api/v0/logic/kb/save", post(api_logic_kb_save))
330 .route("/api/v0/logic/kb/load", post(api_logic_kb_load))
331 .route("/api/v0/id", get(api_network_id))
333 .route("/api/v0/swarm/peers", get(api_swarm_peers))
334 .route("/api/v0/swarm/connect", post(api_swarm_connect))
335 .route("/api/v0/swarm/disconnect", post(api_swarm_disconnect))
336 .route("/api/v0/dht/findprovs", post(api_dht_findprovs))
337 .route("/api/v0/dht/provide", post(api_dht_provide))
338 .route("/v1/stream/download/:cid", get(streaming::stream_download))
340 .route("/v1/stream/upload", post(streaming::stream_upload))
341 .route(
342 "/v1/progress/:operation_id",
343 get(streaming::progress_stream),
344 )
345 .route("/v1/block/batch/get", post(streaming::batch_get))
347 .route("/v1/block/batch/put", post(streaming::batch_put))
348 .route("/v1/block/batch/has", post(streaming::batch_has))
349 .route("/v1/tensor/:cid", get(tensor::get_tensor))
351 .route("/v1/tensor/:cid/info", get(tensor::get_tensor_info))
352 .route("/v1/tensor/:cid/arrow", get(tensor::get_tensor_arrow));
353
354 if self.state.auth.is_some() {
356 router = router
357 .route("/api/v0/auth/me", get(auth_handlers::me_handler))
358 .route(
359 "/api/v0/auth/permissions",
360 post(auth_handlers::update_permissions_handler),
361 )
362 .route(
363 "/api/v0/auth/deactivate/:username",
364 post(auth_handlers::deactivate_user_handler),
365 )
366 .route(
368 "/api/v0/auth/keys",
369 post(auth_handlers::create_api_key_handler),
370 )
371 .route(
372 "/api/v0/auth/keys",
373 get(auth_handlers::list_api_keys_handler),
374 )
375 .route(
376 "/api/v0/auth/keys/:key_id/revoke",
377 post(auth_handlers::revoke_api_key_handler),
378 )
379 .route(
380 "/api/v0/auth/keys/:key_id",
381 axum::routing::delete(auth_handlers::delete_api_key_handler),
382 );
383 }
384
385 router
389 .with_state(self.state.clone())
390 .layer(TraceLayer::new_for_http())
391 }
392
393 pub async fn start(self) -> CoreResult<()> {
395 let app = self.router();
396
397 self.print_endpoints();
399
400 if let Some(ref tls_config) = self.config.tls_config {
402 info!(
403 "Starting IPFRS HTTPS Gateway on {}",
404 self.config.listen_addr
405 );
406
407 let rustls_config = tls_config.build_server_config().await.map_err(|e| {
409 ipfrs_core::Error::Internal(format!("TLS configuration error: {}", e))
410 })?;
411
412 let addr: std::net::SocketAddr = self
414 .config
415 .listen_addr
416 .parse()
417 .map_err(|e| ipfrs_core::Error::Internal(format!("Invalid address: {}", e)))?;
418
419 info!("Gateway listening on https://{}", self.config.listen_addr);
420 info!("TLS/SSL enabled");
421
422 axum_server::bind_rustls(addr, rustls_config)
424 .serve(app.into_make_service())
425 .await
426 .map_err(|e| ipfrs_core::Error::Internal(format!("HTTPS server error: {}", e)))?;
427 } else {
428 info!("Starting IPFRS HTTP Gateway on {}", self.config.listen_addr);
429
430 let listener = tokio::net::TcpListener::bind(&self.config.listen_addr)
431 .await
432 .map_err(|e| {
433 ipfrs_core::Error::Internal(format!("Failed to bind to address: {}", e))
434 })?;
435
436 info!("Gateway listening on http://{}", self.config.listen_addr);
437 info!("Warning: TLS not enabled, using plain HTTP");
438
439 axum::serve(listener, app)
441 .await
442 .map_err(|e| ipfrs_core::Error::Internal(format!("HTTP server error: {}", e)))?;
443 }
444
445 Ok(())
446 }
447
448 fn print_endpoints(&self) {
450 info!("Endpoints:");
451 info!(" GET /health - Health check");
452 info!(" GET /ipfs/{{cid}} - Retrieve content");
453
454 if self.state.auth.is_some() {
456 info!(" POST /api/v0/auth/login - User login");
457 info!(" POST /api/v0/auth/register - User registration");
458 info!(" GET /api/v0/auth/me - Current user info");
459 info!(" POST /api/v0/auth/permissions - Update permissions (admin)");
460 info!(" POST /api/v0/auth/deactivate/:user - Deactivate user (admin)");
461 info!(" POST /api/v0/auth/keys - Create API key");
462 info!(" GET /api/v0/auth/keys - List API keys");
463 info!(" POST /api/v0/auth/keys/:id/revoke - Revoke API key");
464 info!(" DEL /api/v0/auth/keys/:id - Delete API key");
465 }
466
467 info!(" POST /api/v0/version - Get version");
468 info!(" POST /api/v0/add - Upload file");
469 info!(" POST /api/v0/block/get - Get block");
470 info!(" POST /api/v0/block/put - Store raw block");
471 info!(" POST /api/v0/block/stat - Get block stats");
472 info!(" POST /api/v0/cat - Output content");
473 info!(" POST /api/v0/dag/get - Get DAG node");
474 info!(" POST /api/v0/dag/put - Store DAG node");
475 info!(" POST /api/v0/dag/resolve - Resolve IPLD path");
476 info!(" POST /api/v0/semantic/index - Index content");
477 info!(" POST /api/v0/semantic/search - Search similar");
478 info!(" GET /api/v0/semantic/stats - Semantic stats");
479 info!(" POST /api/v0/semantic/save - Save semantic index");
480 info!(" POST /api/v0/semantic/load - Load semantic index");
481 info!(" POST /api/v0/logic/term - Store term");
482 info!(" GET /api/v0/logic/term/{{cid}} - Get term");
483 info!(" POST /api/v0/logic/predicate - Store predicate");
484 info!(" POST /api/v0/logic/rule - Store rule");
485 info!(" GET /api/v0/logic/stats - Logic stats");
486 info!(" POST /api/v0/logic/kb/save - Save knowledge base");
487 info!(" POST /api/v0/logic/kb/load - Load knowledge base");
488 info!(" GET /api/v0/id - Show peer ID");
489 info!(" GET /api/v0/swarm/peers - List peers");
490 info!(" POST /api/v0/swarm/connect - Connect to peer");
491 info!(" POST /api/v0/swarm/disconnect - Disconnect peer");
492 info!(" POST /api/v0/dht/findprovs - Find providers");
493 info!(" POST /api/v0/dht/provide - Announce content");
494
495 info!(" GET /v1/stream/download/:cid - Stream download");
497 info!(" POST /v1/stream/upload - Stream upload");
498 info!(" GET /v1/progress/:operation_id - SSE progress");
499
500 info!(" POST /v1/block/batch/get - Batch get blocks");
502 info!(" POST /v1/block/batch/put - Batch put blocks");
503 info!(" POST /v1/block/batch/has - Batch check blocks");
504 }
505
506 pub fn with_graphql(mut self) -> Self {
508 self.state = self.state.with_graphql();
509 self
510 }
511
512 pub fn with_auth(
514 mut self,
515 secret: &[u8],
516 default_admin_password: Option<&str>,
517 ) -> CoreResult<Self> {
518 self.state = self.state.with_auth(secret, default_admin_password)?;
519 Ok(self)
520 }
521
522 pub fn with_semantic(mut self, config: RouterConfig) -> CoreResult<Self> {
524 self.state = self.state.with_semantic(config)?;
525 Ok(self)
526 }
527
528 pub fn with_tensorlogic(mut self) -> CoreResult<Self> {
530 self.state = self.state.with_tensorlogic()?;
531 Ok(self)
532 }
533
534 pub fn with_network(mut self, network: ipfrs_network::NetworkNode) -> Self {
536 self.state = self.state.with_network(network);
537 self
538 }
539}
540
541pub(crate) mod routes;
542
543#[allow(unused_imports)]
544use routes::*;
545
546#[derive(Debug)]
552enum AppError {
553 InvalidCid(String),
554 BlockNotFound(String),
555 NotFound(String),
556 Upload(String),
557 Storage(ipfrs_core::Error),
558 FeatureDisabled(String),
559 Semantic(String),
560 Logic(String),
561 Network(String),
562}
563
564impl From<ipfrs_core::Error> for AppError {
565 fn from(err: ipfrs_core::Error) -> Self {
566 AppError::Storage(err)
567 }
568}
569
570impl IntoResponse for AppError {
571 fn into_response(self) -> Response {
572 let (status, message) = match self {
573 AppError::InvalidCid(cid) => (StatusCode::BAD_REQUEST, format!("Invalid CID: {}", cid)),
574 AppError::BlockNotFound(cid) => {
575 (StatusCode::NOT_FOUND, format!("Block not found: {}", cid))
576 }
577 AppError::NotFound(msg) => (StatusCode::NOT_FOUND, msg),
578 AppError::Upload(msg) => {
579 error!("Upload error: {}", msg);
580 (StatusCode::BAD_REQUEST, format!("Upload error: {}", msg))
581 }
582 AppError::Storage(err) => {
583 error!("Storage error: {}", err);
584 (
585 StatusCode::INTERNAL_SERVER_ERROR,
586 format!("Storage error: {}", err),
587 )
588 }
589 AppError::FeatureDisabled(msg) => (
590 StatusCode::SERVICE_UNAVAILABLE,
591 format!("Feature not available: {}", msg),
592 ),
593 AppError::Semantic(msg) => {
594 error!("Semantic error: {}", msg);
595 (
596 StatusCode::INTERNAL_SERVER_ERROR,
597 format!("Semantic error: {}", msg),
598 )
599 }
600 AppError::Logic(msg) => {
601 error!("Logic error: {}", msg);
602 (
603 StatusCode::INTERNAL_SERVER_ERROR,
604 format!("Logic error: {}", msg),
605 )
606 }
607 AppError::Network(msg) => {
608 error!("Network error: {}", msg);
609 (
610 StatusCode::INTERNAL_SERVER_ERROR,
611 format!("Network error: {}", msg),
612 )
613 }
614 };
615
616 (status, message).into_response()
617 }
618}
619
620#[cfg(test)]
621mod tests {
622 use super::routes::{build_multipart_response, merge_ranges, parse_multi_range, parse_range};
623 use super::*;
624 use crate::middleware::CacheConfig;
625 use axum::http::header;
626
627 #[test]
628 fn test_parse_single_range() {
629 assert_eq!(parse_range("bytes=0-100", 1000), Some((0, 101)));
631
632 assert_eq!(parse_range("bytes=500-", 1000), Some((500, 1000)));
634
635 assert_eq!(parse_range("bytes=1000-1100", 1000), None);
637
638 assert_eq!(parse_range("bytes=500-100", 1000), None);
640
641 assert_eq!(parse_range("bytes=abc-100", 1000), None);
643 assert_eq!(parse_range("invalid", 1000), None);
644 }
645
646 #[test]
647 fn test_parse_multi_range() {
648 let ranges = parse_multi_range("bytes=0-100", 1000);
650 assert_eq!(ranges, Some(vec![(0, 101)]));
651
652 let ranges = parse_multi_range("bytes=0-100,200-300", 1000);
654 assert_eq!(ranges, Some(vec![(0, 101), (200, 301)]));
655
656 let ranges = parse_multi_range("bytes=0-100, 200-300, 500-600", 1000);
658 assert_eq!(ranges, Some(vec![(0, 101), (200, 301), (500, 601)]));
659
660 let ranges = parse_multi_range("bytes=-500", 1000);
662 assert_eq!(ranges, Some(vec![(500, 1000)]));
663
664 assert_eq!(parse_multi_range("bytes=1000-1100", 1000), None);
666
667 assert_eq!(parse_multi_range("invalid", 1000), None);
669 }
670
671 #[test]
672 fn test_merge_ranges() {
673 let ranges = vec![(0, 100), (200, 300)];
675 assert_eq!(merge_ranges(ranges), vec![(0, 100), (200, 300)]);
676
677 let ranges = vec![(0, 150), (100, 200)];
679 assert_eq!(merge_ranges(ranges), vec![(0, 200)]);
680
681 let ranges = vec![(0, 100), (100, 200)];
683 assert_eq!(merge_ranges(ranges), vec![(0, 200)]);
684
685 let ranges = vec![(200, 300), (0, 100), (50, 150)];
687 assert_eq!(merge_ranges(ranges), vec![(0, 150), (200, 300)]);
688
689 let ranges = vec![(50, 100)];
691 assert_eq!(merge_ranges(ranges), vec![(50, 100)]);
692
693 let ranges: Vec<(usize, usize)> = vec![];
695 assert_eq!(merge_ranges(ranges), vec![]);
696 }
697
698 #[test]
699 fn test_build_multipart_response() {
700 let data = b"Hello, World! This is test data for multi-range requests.";
701 let ranges = vec![(0, 5), (7, 12)];
702 let total_size = data.len();
703 let config = CacheConfig::default();
704
705 let response = build_multipart_response(data, &ranges, total_size, "QmTest123", &config);
706
707 assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT);
708
709 let content_type = response
710 .headers()
711 .get(header::CONTENT_TYPE)
712 .expect("test: CONTENT_TYPE header must be present")
713 .to_str()
714 .expect("test: CONTENT_TYPE header value must be valid UTF-8");
715 assert!(content_type.starts_with("multipart/byteranges"));
716 assert!(content_type.contains("boundary="));
717
718 assert!(response.headers().contains_key(header::ETAG));
720 assert!(response.headers().contains_key(header::CACHE_CONTROL));
721 }
722
723 #[test]
724 fn test_config_presets() {
725 let prod = GatewayConfig::production();
727 assert_eq!(prod.listen_addr, "0.0.0.0:8080");
728 assert!(prod.compression_config.enable_gzip);
729
730 let dev = GatewayConfig::development();
732 assert_eq!(dev.listen_addr, "127.0.0.1:8080");
733 assert!(dev.compression_config.enable_gzip);
734
735 let test = GatewayConfig::testing();
737 assert_eq!(test.listen_addr, "127.0.0.1:0");
738 assert!(!test.compression_config.enable_gzip);
739 }
740
741 #[test]
742 fn test_config_builders() {
743 let config = GatewayConfig::default()
744 .with_listen_addr("0.0.0.0:9090")
745 .with_storage_path("/custom/path")
746 .with_cache_mb(200)
747 .with_full_compression();
748
749 assert_eq!(config.listen_addr, "0.0.0.0:9090");
750 assert!(config.compression_config.enable_gzip);
751 }
752
753 #[test]
754 fn test_config_validation() {
755 let valid_config = GatewayConfig::default();
757 assert!(valid_config.validate().is_ok());
758
759 let invalid_addr = GatewayConfig {
761 listen_addr: "invalid-address".to_string(),
762 ..Default::default()
763 };
764 assert!(invalid_addr.validate().is_err());
765
766 let empty_addr = GatewayConfig {
768 listen_addr: "".to_string(),
769 ..Default::default()
770 };
771 assert!(empty_addr.validate().is_err());
772 }
773
774 #[test]
775 fn test_compression_helpers() {
776 let config_with = GatewayConfig::default().with_full_compression();
777 assert!(config_with.compression_config.enable_gzip);
778
779 let config_without = GatewayConfig::default().without_compression();
780 assert!(!config_without.compression_config.enable_gzip);
781 }
782}