Skip to main content

slim_bindings/
server_config.rs

1// Copyright AGNTCY Contributors (https://github.com/agntcy)
2// SPDX-License-Identifier: Apache-2.0
3
4use std::time::Duration;
5
6use slim_auth::metadata::MetadataMap;
7use slim_config::grpc::server::KeepaliveServerParameters as CoreKeepaliveServerParameters;
8use slim_config::grpc::server::ServerConfig as CoreServerConfig;
9
10use crate::common_config::{ServerAuthenticationConfig, TlsServerConfig, TlsSource};
11use crate::transport_protocol::TransportProtocol;
12
13/// Keepalive configuration for the server
14#[derive(uniffi::Record, Clone, Debug, PartialEq)]
15pub struct KeepaliveServerParameters {
16    /// Max connection idle time (time after which an idle connection is closed)
17    pub max_connection_idle: Duration,
18
19    /// Max connection age (maximum time a connection may exist before being closed)
20    pub max_connection_age: Duration,
21
22    /// Max connection age grace (additional time after max_connection_age before closing)
23    pub max_connection_age_grace: Duration,
24
25    /// Keepalive ping frequency
26    pub time: Duration,
27
28    /// Keepalive ping timeout (time to wait for ack)
29    pub timeout: Duration,
30}
31
32impl Default for KeepaliveServerParameters {
33    fn default() -> Self {
34        let core_defaults = CoreKeepaliveServerParameters::default();
35        KeepaliveServerParameters {
36            max_connection_idle: *core_defaults.max_connection_idle,
37            max_connection_age: *core_defaults.max_connection_age,
38            max_connection_age_grace: *core_defaults.max_connection_age_grace,
39            time: *core_defaults.time,
40            timeout: *core_defaults.timeout,
41        }
42    }
43}
44
45impl From<KeepaliveServerParameters> for CoreKeepaliveServerParameters {
46    fn from(config: KeepaliveServerParameters) -> Self {
47        CoreKeepaliveServerParameters {
48            max_connection_idle: config.max_connection_idle.into(),
49            max_connection_age: config.max_connection_age.into(),
50            max_connection_age_grace: config.max_connection_age_grace.into(),
51            time: config.time.into(),
52            timeout: config.timeout.into(),
53        }
54    }
55}
56
57impl From<CoreKeepaliveServerParameters> for KeepaliveServerParameters {
58    fn from(config: CoreKeepaliveServerParameters) -> Self {
59        KeepaliveServerParameters {
60            max_connection_idle: *config.max_connection_idle,
61            max_connection_age: *config.max_connection_age,
62            max_connection_age_grace: *config.max_connection_age_grace,
63            time: *config.time,
64            timeout: *config.timeout,
65        }
66    }
67}
68
69/// Server configuration for running a SLIM server
70#[derive(uniffi::Record, Clone, Debug, PartialEq)]
71pub struct ServerConfig {
72    /// Endpoint address to listen on (e.g., "0.0.0.0:50051" or "[::]:50051")
73    pub endpoint: String,
74
75    /// Transport protocol to use (defaults to gRPC in core config when omitted)
76    pub transport: Option<TransportProtocol>,
77
78    /// TLS server configuration
79    pub tls: TlsServerConfig,
80
81    /// Use HTTP/2 only (default: true)
82    pub http2_only: Option<bool>,
83
84    /// Maximum size (in MiB) of messages accepted by the server
85    pub max_frame_size: Option<u32>,
86
87    /// Maximum number of concurrent streams per connection
88    pub max_concurrent_streams: Option<u32>,
89
90    /// Maximum header list size in bytes
91    pub max_header_list_size: Option<u32>,
92
93    /// Read buffer size in bytes
94    pub read_buffer_size: Option<u64>,
95
96    /// Write buffer size in bytes
97    pub write_buffer_size: Option<u64>,
98
99    /// Keepalive parameters
100    pub keepalive: Option<KeepaliveServerParameters>,
101
102    /// Authentication configuration for incoming requests
103    pub auth: Option<ServerAuthenticationConfig>,
104
105    /// Arbitrary user-provided metadata as JSON string
106    pub metadata: Option<String>,
107}
108
109impl Default for ServerConfig {
110    fn default() -> Self {
111        let core_defaults = CoreServerConfig::default();
112        ServerConfig {
113            endpoint: core_defaults.endpoint,
114            transport: None,
115            tls: core_defaults.tls_setting.into(),
116            http2_only: None,
117            max_frame_size: None,
118            max_concurrent_streams: None,
119            max_header_list_size: None,
120            read_buffer_size: None,
121            write_buffer_size: None,
122            keepalive: None,
123            auth: None,
124            metadata: None,
125        }
126    }
127}
128
129impl From<ServerConfig> for CoreServerConfig {
130    fn from(config: ServerConfig) -> Self {
131        let core_defaults = CoreServerConfig::default();
132        CoreServerConfig {
133            endpoint: config.endpoint,
134            transport: config
135                .transport
136                .map(Into::into)
137                .unwrap_or(core_defaults.transport),
138            tls_setting: config.tls.into(),
139            http2_only: config.http2_only.unwrap_or(core_defaults.http2_only),
140            max_frame_size: config.max_frame_size.or(core_defaults.max_frame_size),
141            max_concurrent_streams: config
142                .max_concurrent_streams
143                .or(core_defaults.max_concurrent_streams),
144            max_header_list_size: config
145                .max_header_list_size
146                .or(core_defaults.max_header_list_size),
147            read_buffer_size: config
148                .read_buffer_size
149                .map(|s| s as usize)
150                .or(core_defaults.read_buffer_size),
151            write_buffer_size: config
152                .write_buffer_size
153                .map(|s| s as usize)
154                .or(core_defaults.write_buffer_size),
155            keepalive: config
156                .keepalive
157                .map(Into::into)
158                .unwrap_or(core_defaults.keepalive),
159            auth: config.auth.map(Into::into).unwrap_or(core_defaults.auth),
160            metadata: config
161                .metadata
162                .and_then(|json| serde_json::from_str::<MetadataMap>(&json).ok()),
163        }
164    }
165}
166
167impl From<CoreServerConfig> for ServerConfig {
168    fn from(config: CoreServerConfig) -> Self {
169        ServerConfig {
170            endpoint: config.endpoint,
171            transport: Some(config.transport.into()),
172            tls: config.tls_setting.into(),
173            http2_only: Some(config.http2_only),
174            max_frame_size: config.max_frame_size,
175            max_concurrent_streams: config.max_concurrent_streams,
176            max_header_list_size: config.max_header_list_size,
177            read_buffer_size: config.read_buffer_size.map(|s| s as u64),
178            write_buffer_size: config.write_buffer_size.map(|s| s as u64),
179            keepalive: Some(config.keepalive.into()),
180            auth: Some(config.auth.into()),
181            metadata: config.metadata.and_then(|m| serde_json::to_string(&m).ok()),
182        }
183    }
184}
185
186/// Create a new server config with the given endpoint and default values
187#[uniffi::export]
188pub fn new_server_config(endpoint: String) -> ServerConfig {
189    ServerConfig {
190        endpoint,
191        ..Default::default()
192    }
193}
194
195/// Create a new insecure server config (no TLS)
196#[uniffi::export]
197pub fn new_insecure_server_config(endpoint: String) -> ServerConfig {
198    ServerConfig {
199        endpoint,
200        tls: TlsServerConfig {
201            insecure: true,
202            ..Default::default()
203        },
204        ..Default::default()
205    }
206}
207
208/// Create a new secure server config (TLS enabled with the given certificate source)
209#[uniffi::export]
210pub fn new_secure_server_config(endpoint: String, tls_source: TlsSource) -> ServerConfig {
211    ServerConfig {
212        endpoint,
213        tls: TlsServerConfig {
214            insecure: false,
215            source: tls_source,
216            ..Default::default()
217        },
218        ..Default::default()
219    }
220}
221
222#[cfg(test)]
223mod tests {
224    use super::*;
225    use crate::common_config::{CaSource, TlsSource};
226    use slim_config::transport::TransportProtocol as CoreTransportProtocol;
227
228    #[test]
229    fn test_server_config_creation() {
230        let config = ServerConfig {
231            endpoint: "127.0.0.1:8080".to_string(),
232            tls: TlsServerConfig {
233                insecure: false,
234                source: TlsSource::File {
235                    cert: "/cert.pem".to_string(),
236                    key: "/key.pem".to_string(),
237                },
238                client_ca: CaSource::None,
239                include_system_ca_certs_pool: Some(true),
240                tls_version: Some("tls1.3".to_string()),
241                reload_client_ca_file: Some(false),
242            },
243            http2_only: Some(true),
244            ..Default::default()
245        };
246
247        assert_eq!(config.endpoint, "127.0.0.1:8080");
248        assert!(!config.tls.insecure);
249        assert_eq!(config.tls.tls_version, Some("tls1.3".to_string()));
250        assert_eq!(config.http2_only, Some(true));
251    }
252
253    #[test]
254    fn test_server_config_default() {
255        let config = ServerConfig::default();
256
257        // Verify defaults are all None (core defaults applied during conversion)
258        assert_eq!(config.endpoint, "");
259        assert_eq!(config.transport, None);
260        assert_eq!(config.http2_only, None);
261        assert_eq!(config.max_frame_size, None);
262        assert_eq!(config.max_concurrent_streams, None);
263        assert_eq!(config.max_header_list_size, None);
264        assert_eq!(config.read_buffer_size, None);
265        assert_eq!(config.write_buffer_size, None);
266        assert_eq!(config.keepalive, None);
267        assert_eq!(config.auth, None);
268        assert_eq!(config.metadata, None);
269
270        // Verify core defaults are applied when converting to CoreServerConfig
271        let core: CoreServerConfig = config.into();
272        assert!(core.http2_only);
273        assert_eq!(core.max_frame_size, Some(4));
274        assert_eq!(core.max_concurrent_streams, Some(100));
275        assert_eq!(core.read_buffer_size, Some(1024 * 1024));
276        assert_eq!(core.write_buffer_size, Some(1024 * 1024));
277        assert_eq!(
278            core.auth,
279            slim_config::grpc::server::AuthenticationConfig::None
280        );
281    }
282
283    #[test]
284    fn test_server_config_new() {
285        let config = new_server_config("0.0.0.0:50051".to_string());
286
287        assert_eq!(config.endpoint, "0.0.0.0:50051");
288        // Other fields should be None defaults
289        assert_eq!(config.http2_only, None);
290        assert_eq!(config.auth, None);
291    }
292
293    #[test]
294    fn test_server_config_new_insecure() {
295        let config = new_insecure_server_config("[::]:50051".to_string());
296
297        assert_eq!(config.endpoint, "[::]:50051");
298        assert!(config.tls.insecure);
299        assert_eq!(config.http2_only, None);
300    }
301
302    #[test]
303    fn test_server_config_new_secure() {
304        let config = new_secure_server_config(
305            "0.0.0.0:443".to_string(),
306            TlsSource::File {
307                cert: "/etc/tls/server.crt".to_string(),
308                key: "/etc/tls/server.key".to_string(),
309            },
310        );
311
312        assert_eq!(config.endpoint, "0.0.0.0:443");
313        assert!(!config.tls.insecure);
314        assert_eq!(
315            config.tls.source,
316            TlsSource::File {
317                cert: "/etc/tls/server.crt".to_string(),
318                key: "/etc/tls/server.key".to_string(),
319            }
320        );
321        // All optional fields should be None
322        assert_eq!(config.http2_only, None);
323        assert_eq!(config.max_frame_size, None);
324        assert_eq!(config.max_concurrent_streams, None);
325        assert_eq!(config.keepalive, None);
326        assert_eq!(config.auth, None);
327        assert_eq!(config.metadata, None);
328    }
329
330    #[test]
331    fn test_server_config_to_core_conversion() {
332        let ffi_config = ServerConfig {
333            endpoint: "127.0.0.1:8080".to_string(),
334            transport: Some(TransportProtocol::Websocket),
335            tls: TlsServerConfig::default(),
336            http2_only: Some(false),
337            max_frame_size: Some(8),
338            max_concurrent_streams: Some(200),
339            max_header_list_size: Some(8192),
340            read_buffer_size: Some(2048),
341            write_buffer_size: Some(2048),
342            keepalive: Some(KeepaliveServerParameters::default()),
343            auth: Some(ServerAuthenticationConfig::None),
344            metadata: Some(r#"{"key":"value"}"#.to_string()),
345        };
346
347        let core_config: CoreServerConfig = ffi_config.into();
348
349        assert_eq!(core_config.endpoint, "127.0.0.1:8080");
350        assert_eq!(core_config.transport, CoreTransportProtocol::Websocket);
351        assert!(!core_config.http2_only);
352        assert_eq!(core_config.max_frame_size, Some(8));
353        assert_eq!(core_config.max_concurrent_streams, Some(200));
354        assert_eq!(core_config.max_header_list_size, Some(8192));
355        assert_eq!(core_config.read_buffer_size, Some(2048));
356        assert_eq!(core_config.write_buffer_size, Some(2048));
357        assert!(core_config.metadata.is_some());
358    }
359
360    #[test]
361    fn test_server_config_from_core_conversion() {
362        // Test the new From<CoreServerConfig> for ServerConfig implementation
363        let core_config = CoreServerConfig::default();
364
365        // Use the From trait to convert
366        let ffi_config: ServerConfig = core_config.clone().into();
367
368        assert_eq!(ffi_config.endpoint, core_config.endpoint);
369        assert_eq!(ffi_config.transport, Some(core_config.transport.into()));
370        assert_eq!(ffi_config.http2_only, Some(core_config.http2_only));
371        assert_eq!(ffi_config.max_frame_size, core_config.max_frame_size);
372        assert_eq!(
373            ffi_config.max_concurrent_streams,
374            core_config.max_concurrent_streams
375        );
376        assert_eq!(
377            ffi_config.max_header_list_size,
378            core_config.max_header_list_size
379        );
380    }
381
382    #[test]
383    fn test_server_config_roundtrip_conversion() {
384        let original = ServerConfig {
385            endpoint: "localhost:9090".to_string(),
386            transport: Some(TransportProtocol::Grpc),
387            tls: TlsServerConfig::default(),
388            http2_only: Some(true),
389            max_frame_size: Some(16),
390            max_concurrent_streams: Some(500),
391            max_header_list_size: Some(16384),
392            read_buffer_size: Some(4096),
393            write_buffer_size: Some(4096),
394            keepalive: Some(KeepaliveServerParameters::default()),
395            auth: Some(ServerAuthenticationConfig::None),
396            metadata: None,
397        };
398
399        // FFI -> Core -> FFI using the new From implementation
400        let core: CoreServerConfig = original.clone().into();
401        let roundtrip: ServerConfig = core.into();
402
403        assert_eq!(roundtrip.endpoint, original.endpoint);
404        assert_eq!(roundtrip.transport, original.transport);
405        assert_eq!(roundtrip.http2_only, original.http2_only);
406        assert_eq!(roundtrip.max_frame_size, original.max_frame_size);
407        assert_eq!(
408            roundtrip.max_concurrent_streams,
409            original.max_concurrent_streams
410        );
411        assert_eq!(
412            roundtrip.max_header_list_size,
413            original.max_header_list_size
414        );
415        assert_eq!(roundtrip.read_buffer_size, original.read_buffer_size);
416        assert_eq!(roundtrip.write_buffer_size, original.write_buffer_size);
417    }
418
419    #[test]
420    fn test_keepalive_defaults() {
421        let keepalive = KeepaliveServerParameters::default();
422
423        // Verify keepalive has reasonable defaults
424        assert!(keepalive.max_connection_idle.as_secs() > 0);
425        assert!(keepalive.max_connection_age.as_secs() > 0);
426        assert!(keepalive.time.as_secs() > 0);
427        assert!(keepalive.timeout.as_secs() > 0);
428    }
429
430    #[test]
431    fn test_keepalive_conversion() {
432        let ffi_keepalive = KeepaliveServerParameters {
433            max_connection_idle: Duration::from_secs(600),
434            max_connection_age: Duration::from_secs(1800),
435            max_connection_age_grace: Duration::from_secs(60),
436            time: Duration::from_secs(300),
437            timeout: Duration::from_secs(20),
438        };
439
440        let core_keepalive: CoreKeepaliveServerParameters = ffi_keepalive.into();
441
442        assert_eq!(
443            *core_keepalive.max_connection_idle,
444            Duration::from_secs(600)
445        );
446        assert_eq!(
447            *core_keepalive.max_connection_age,
448            Duration::from_secs(1800)
449        );
450        assert_eq!(
451            *core_keepalive.max_connection_age_grace,
452            Duration::from_secs(60)
453        );
454        assert_eq!(*core_keepalive.time, Duration::from_secs(300));
455        assert_eq!(*core_keepalive.timeout, Duration::from_secs(20));
456    }
457
458    #[test]
459    fn test_metadata_serialization() {
460        let config = ServerConfig {
461            endpoint: "test:8080".to_string(),
462            tls: TlsServerConfig::default(),
463            metadata: Some(r#"{"env":"test","version":1}"#.to_string()),
464            ..Default::default()
465        };
466
467        let core: CoreServerConfig = config.into();
468
469        // Metadata should be deserialized successfully
470        assert!(core.metadata.is_some());
471        let metadata = core.metadata.unwrap();
472        assert_eq!(metadata.len(), 2);
473    }
474
475    #[test]
476    fn test_metadata_invalid_json() {
477        let config = ServerConfig {
478            endpoint: "test:8080".to_string(),
479            tls: TlsServerConfig::default(),
480            metadata: Some("invalid json".to_string()),
481            ..Default::default()
482        };
483
484        let core: CoreServerConfig = config.into();
485
486        // Invalid JSON should result in None metadata
487        assert!(core.metadata.is_none());
488    }
489
490    #[test]
491    fn test_basic_auth_roundtrip() {
492        use crate::common_config::BasicAuth;
493
494        let basic_config = BasicAuth {
495            username: "admin".to_string(),
496            password: "secret123".to_string(),
497        };
498
499        let auth = ServerAuthenticationConfig::Basic {
500            config: basic_config.clone(),
501        };
502
503        // Convert to core and back
504        let core_auth: slim_config::grpc::server::AuthenticationConfig = auth.into();
505        let roundtrip_auth: ServerAuthenticationConfig = core_auth.into();
506
507        // Verify roundtrip preserves the configuration
508        if let ServerAuthenticationConfig::Basic { config } = roundtrip_auth {
509            assert_eq!(config.username, basic_config.username);
510            assert_eq!(config.password, basic_config.password);
511        } else {
512            panic!("Expected Basic authentication config");
513        }
514    }
515
516    #[test]
517    fn test_jwt_auth_roundtrip() {
518        use crate::identity_config::{
519            JwtAlgorithm, JwtAuth, JwtKeyConfig, JwtKeyData, JwtKeyFormat, JwtKeyType,
520        };
521
522        let jwt_config = JwtAuth {
523            key: JwtKeyType::Decoding {
524                key: JwtKeyConfig {
525                    algorithm: JwtAlgorithm::RS256,
526                    format: JwtKeyFormat::Pem,
527                    key: JwtKeyData::File {
528                        path: "/path/to/public_key.pem".to_string(),
529                    },
530                },
531            },
532            audience: Some(vec!["api.example.com".to_string()]),
533            issuer: Some("auth.example.com".to_string()),
534            subject: Some("service123".to_string()),
535            duration: Duration::from_secs(7200),
536        };
537
538        let auth = ServerAuthenticationConfig::Jwt {
539            config: jwt_config.clone(),
540        };
541
542        // Convert to core and back
543        let core_auth: slim_config::grpc::server::AuthenticationConfig = auth.into();
544        let roundtrip_auth: ServerAuthenticationConfig = core_auth.into();
545
546        // Verify roundtrip preserves the configuration
547        if let ServerAuthenticationConfig::Jwt { config } = roundtrip_auth {
548            assert_eq!(config.key, jwt_config.key);
549            assert_eq!(config.audience, jwt_config.audience);
550            assert_eq!(config.issuer, jwt_config.issuer);
551            assert_eq!(config.subject, jwt_config.subject);
552            // Note: duration might not be exactly preserved due to conversion limitations
553        } else {
554            panic!("Expected Jwt authentication config");
555        }
556    }
557
558    #[test]
559    fn test_server_config_from_core_with_all_fields() {
560        // Test the new From<CoreServerConfig> for ServerConfig with comprehensive field coverage
561        use slim_auth::metadata::MetadataMap;
562
563        let mut metadata = MetadataMap::new();
564        metadata.insert("service".to_string(), "test-server".to_string());
565        metadata.insert("region".to_string(), "us-east-1".to_string());
566
567        let core_config = CoreServerConfig {
568            endpoint: "0.0.0.0:8443".to_string(),
569            http2_only: false,
570            max_frame_size: Some(32),
571            max_concurrent_streams: Some(1000),
572            max_header_list_size: Some(32768),
573            read_buffer_size: Some(16384),
574            write_buffer_size: Some(16384),
575            metadata: Some(metadata),
576            ..Default::default()
577        };
578
579        // Use the new From implementation
580        let ffi_config: ServerConfig = core_config.clone().into();
581
582        // Verify all fields are correctly converted
583        assert_eq!(ffi_config.endpoint, "0.0.0.0:8443");
584        assert_eq!(ffi_config.http2_only, Some(false));
585        assert_eq!(ffi_config.max_frame_size, Some(32));
586        assert_eq!(ffi_config.max_concurrent_streams, Some(1000));
587        assert_eq!(ffi_config.max_header_list_size, Some(32768));
588        assert_eq!(ffi_config.read_buffer_size, Some(16384));
589        assert_eq!(ffi_config.write_buffer_size, Some(16384));
590
591        // Verify metadata is serialized correctly
592        assert!(ffi_config.metadata.is_some());
593        let metadata_str = ffi_config.metadata.unwrap();
594        assert!(metadata_str.contains("test-server"));
595        assert!(metadata_str.contains("us-east-1"));
596    }
597
598    #[test]
599    fn test_server_config_from_core_with_keepalive() {
600        use slim_config::grpc::server::KeepaliveServerParameters as CoreKeepaliveServerParameters;
601
602        let core_config = CoreServerConfig {
603            keepalive: CoreKeepaliveServerParameters {
604                max_connection_idle: Duration::from_secs(300).into(),
605                max_connection_age: Duration::from_secs(900).into(),
606                max_connection_age_grace: Duration::from_secs(30).into(),
607                time: Duration::from_secs(150).into(),
608                timeout: Duration::from_secs(15).into(),
609            },
610            ..Default::default()
611        };
612
613        let ffi_config: ServerConfig = core_config.into();
614
615        let keepalive = ffi_config.keepalive.unwrap();
616        assert_eq!(keepalive.max_connection_idle, Duration::from_secs(300));
617        assert_eq!(keepalive.max_connection_age, Duration::from_secs(900));
618        assert_eq!(keepalive.max_connection_age_grace, Duration::from_secs(30));
619        assert_eq!(keepalive.time, Duration::from_secs(150));
620        assert_eq!(keepalive.timeout, Duration::from_secs(15));
621    }
622
623    #[test]
624    fn test_server_config_from_core_buffer_size_conversion() {
625        // Test that buffer sizes are correctly converted from usize to u64
626        let core_config = CoreServerConfig {
627            read_buffer_size: Some(8192),
628            write_buffer_size: Some(8192),
629            ..Default::default()
630        };
631
632        let ffi_config: ServerConfig = core_config.into();
633
634        assert_eq!(ffi_config.read_buffer_size, Some(8192u64));
635        assert_eq!(ffi_config.write_buffer_size, Some(8192u64));
636    }
637
638    #[test]
639    fn test_server_config_from_core_no_buffer_sizes() {
640        // Test with None buffer sizes
641        let core_config = CoreServerConfig {
642            read_buffer_size: None,
643            write_buffer_size: None,
644            ..Default::default()
645        };
646
647        let ffi_config: ServerConfig = core_config.into();
648
649        assert!(ffi_config.read_buffer_size.is_none());
650        assert!(ffi_config.write_buffer_size.is_none());
651    }
652
653    #[test]
654    fn test_server_config_from_core_http2_only_variations() {
655        // Test http2_only flag both true and false
656        let core_config_true = CoreServerConfig {
657            http2_only: true,
658            ..Default::default()
659        };
660
661        let ffi_config_true: ServerConfig = core_config_true.into();
662        assert_eq!(ffi_config_true.http2_only, Some(true));
663
664        let core_config_false = CoreServerConfig {
665            http2_only: false,
666            ..Default::default()
667        };
668
669        let ffi_config_false: ServerConfig = core_config_false.into();
670        assert_eq!(ffi_config_false.http2_only, Some(false));
671    }
672
673    #[test]
674    fn test_server_config_from_core_with_optional_fields() {
675        // Test with various combinations of optional fields
676        let core_config = CoreServerConfig {
677            max_frame_size: None,
678            max_concurrent_streams: Some(250),
679            max_header_list_size: None,
680            ..Default::default()
681        };
682
683        let ffi_config: ServerConfig = core_config.into();
684
685        assert!(ffi_config.max_frame_size.is_none());
686        assert_eq!(ffi_config.max_concurrent_streams, Some(250));
687        assert!(ffi_config.max_header_list_size.is_none());
688    }
689
690    #[test]
691    fn test_server_config_from_core_metadata_serialization_failure() {
692        // Test that None metadata results in None
693        let core_config = CoreServerConfig {
694            metadata: None,
695            ..Default::default()
696        };
697
698        let ffi_config: ServerConfig = core_config.into();
699
700        assert!(ffi_config.metadata.is_none());
701    }
702
703    #[test]
704    fn test_server_config_from_core_auth_types() {
705        use slim_config::auth::basic::Config as BasicAuthConfig;
706        use slim_config::grpc::server::AuthenticationConfig as CoreAuthConfig;
707
708        // Test with Basic auth
709        let core_config = CoreServerConfig {
710            auth: CoreAuthConfig::Basic(BasicAuthConfig::new("server_user", "server_pass")),
711            ..Default::default()
712        };
713
714        let ffi_config: ServerConfig = core_config.into();
715
716        match ffi_config.auth.unwrap() {
717            ServerAuthenticationConfig::Basic { config } => {
718                assert_eq!(config.username, "server_user");
719                assert_eq!(config.password, "server_pass");
720            }
721            _ => panic!("Expected Basic auth"),
722        }
723    }
724
725    #[test]
726    fn test_server_config_from_core_auth_none() {
727        use slim_config::grpc::server::AuthenticationConfig as CoreAuthConfig;
728
729        // Test with None auth
730        let core_config = CoreServerConfig {
731            auth: CoreAuthConfig::None,
732            ..Default::default()
733        };
734
735        let ffi_config: ServerConfig = core_config.into();
736
737        match ffi_config.auth.unwrap() {
738            ServerAuthenticationConfig::None => {
739                // Success
740            }
741            _ => panic!("Expected None auth"),
742        }
743    }
744
745    #[test]
746    fn test_keepalive_server_parameters_roundtrip() {
747        // Test KeepaliveServerParameters conversion both ways
748        let original = KeepaliveServerParameters {
749            max_connection_idle: Duration::from_secs(500),
750            max_connection_age: Duration::from_secs(1500),
751            max_connection_age_grace: Duration::from_secs(50),
752            time: Duration::from_secs(250),
753            timeout: Duration::from_secs(25),
754        };
755
756        let core: CoreKeepaliveServerParameters = original.clone().into();
757        let roundtrip: KeepaliveServerParameters = core.into();
758
759        assert_eq!(roundtrip.max_connection_idle, original.max_connection_idle);
760        assert_eq!(roundtrip.max_connection_age, original.max_connection_age);
761        assert_eq!(
762            roundtrip.max_connection_age_grace,
763            original.max_connection_age_grace
764        );
765        assert_eq!(roundtrip.time, original.time);
766        assert_eq!(roundtrip.timeout, original.timeout);
767    }
768}