Skip to main content

slim_bindings/
init_config.rs

1// Copyright AGNTCY Contributors (https://github.com/agntcy)
2// SPDX-License-Identifier: Apache-2.0
3
4//! UniFFI-compatible configuration wrappers for initialization
5//!
6//! This module provides wrapper types for RuntimeConfiguration, TracingConfiguration,
7//! and ServiceConfiguration that can be passed through UniFFI bindings.
8
9use std::time::Duration;
10
11use serde::{Deserialize, Serialize};
12use slim::runtime::RuntimeConfiguration as CoreRuntimeConfiguration;
13use slim_tracing::TracingConfiguration as CoreTracingConfiguration;
14
15use crate::{ServiceConfig, service::DataplaneConfig};
16
17/// Runtime configuration for the SLIM bindings
18///
19/// Controls the Tokio runtime behavior including thread count, naming, and shutdown timeout.
20#[derive(uniffi::Record, Clone, Debug, Serialize, Deserialize)]
21pub struct RuntimeConfig {
22    /// Number of cores to use for the runtime (0 = use all available cores)
23    pub n_cores: u64,
24
25    /// Thread name prefix for the runtime
26    pub thread_name: String,
27
28    /// Timeout duration for draining services during shutdown
29    pub drain_timeout: Duration,
30}
31
32impl Default for RuntimeConfig {
33    fn default() -> Self {
34        let core = CoreRuntimeConfiguration::default();
35        RuntimeConfig {
36            n_cores: core.n_cores() as u64,
37            thread_name: core.thread_name().to_string(),
38            drain_timeout: core.drain_timeout(),
39        }
40    }
41}
42
43impl From<RuntimeConfig> for CoreRuntimeConfiguration {
44    fn from(config: RuntimeConfig) -> Self {
45        CoreRuntimeConfiguration {
46            n_cores: config.n_cores as usize,
47            thread_name: config.thread_name,
48            drain_timeout: config.drain_timeout.into(),
49        }
50    }
51}
52
53impl From<CoreRuntimeConfiguration> for RuntimeConfig {
54    fn from(config: CoreRuntimeConfiguration) -> Self {
55        RuntimeConfig {
56            n_cores: config.n_cores() as u64,
57            thread_name: config.thread_name().to_string(),
58            drain_timeout: config.drain_timeout(),
59        }
60    }
61}
62
63/// Tracing/logging configuration for the SLIM bindings
64///
65/// Controls logging behavior including log level, thread name/ID display, and filters.
66#[derive(uniffi::Record, Clone, Debug, Serialize, Deserialize)]
67pub struct TracingConfig {
68    /// Log level (e.g., "debug", "info", "warn", "error")
69    pub log_level: String,
70
71    /// Whether to display thread names in logs
72    pub display_thread_names: bool,
73
74    /// Whether to display thread IDs in logs
75    pub display_thread_ids: bool,
76
77    /// List of tracing filter directives (e.g., ["slim=debug", "tokio=info"])
78    pub filters: Vec<String>,
79}
80
81impl Default for TracingConfig {
82    fn default() -> Self {
83        let core = CoreTracingConfiguration::default();
84        TracingConfig {
85            log_level: core.log_level().to_string(),
86            display_thread_names: core.display_thread_names(),
87            display_thread_ids: core.display_thread_ids(),
88            filters: core.filter().to_vec(),
89        }
90    }
91}
92
93impl From<TracingConfig> for CoreTracingConfiguration {
94    fn from(config: TracingConfig) -> Self {
95        CoreTracingConfiguration::default()
96            .with_log_level(config.log_level)
97            .with_display_thread_names(config.display_thread_names)
98            .with_display_thread_ids(config.display_thread_ids)
99            .with_filter(config.filters)
100    }
101}
102
103impl From<CoreTracingConfiguration> for TracingConfig {
104    fn from(config: CoreTracingConfiguration) -> Self {
105        TracingConfig {
106            log_level: config.log_level().to_string(),
107            display_thread_names: config.display_thread_names(),
108            display_thread_ids: config.display_thread_ids(),
109            filters: config.filter().to_vec(),
110        }
111    }
112}
113
114// Constructor functions for UniFFI
115
116/// Create a new BindingsRuntimeConfig with default values
117#[uniffi::export]
118pub fn new_runtime_config() -> RuntimeConfig {
119    RuntimeConfig::default()
120}
121
122/// Create a new BindingsRuntimeConfig with custom values
123#[uniffi::export]
124pub fn new_runtime_config_with(
125    n_cores: u64,
126    thread_name: String,
127    drain_timeout: Duration,
128) -> RuntimeConfig {
129    RuntimeConfig {
130        n_cores,
131        thread_name,
132        drain_timeout,
133    }
134}
135
136/// Create a new BindingsTracingConfig with default values
137#[uniffi::export]
138pub fn new_tracing_config() -> TracingConfig {
139    TracingConfig::default()
140}
141
142/// Create a new BindingsTracingConfig with custom values
143#[uniffi::export]
144pub fn new_tracing_config_with(
145    log_level: String,
146    display_thread_names: bool,
147    display_thread_ids: bool,
148    filters: Vec<String>,
149) -> TracingConfig {
150    TracingConfig {
151        log_level,
152        display_thread_names,
153        display_thread_ids,
154        filters,
155    }
156}
157
158/// Create a new BindingsServiceConfig with default values
159#[uniffi::export]
160pub fn new_service_config() -> ServiceConfig {
161    ServiceConfig::default()
162}
163
164/// Create a new BindingsServiceConfig with custom values
165#[uniffi::export]
166pub fn new_service_config_with(
167    node_id: Option<String>,
168    group_name: Option<String>,
169    dataplane: DataplaneConfig,
170) -> ServiceConfig {
171    ServiceConfig {
172        node_id,
173        group_name,
174        dataplane,
175    }
176}
177
178#[cfg(test)]
179mod tests {
180    use slim_service::ServiceConfiguration as CoreServiceConfiguration;
181
182    use super::*;
183
184    #[test]
185    fn test_runtime_configuration_default() {
186        let config = RuntimeConfig::default();
187        assert_eq!(config.n_cores, 0); // 0 means use all cores
188        assert_eq!(config.thread_name, "slim");
189        assert_eq!(config.drain_timeout, Duration::from_secs(10));
190    }
191
192    #[test]
193    fn test_runtime_configuration_roundtrip() {
194        let config = RuntimeConfig {
195            n_cores: 4,
196            thread_name: "test-runtime".to_string(),
197            drain_timeout: Duration::from_secs(30),
198        };
199
200        let core: CoreRuntimeConfiguration = config.clone().into();
201        let back: RuntimeConfig = core.into();
202
203        assert_eq!(back.n_cores, config.n_cores);
204        assert_eq!(back.thread_name, config.thread_name);
205        assert_eq!(back.drain_timeout, config.drain_timeout);
206    }
207
208    #[test]
209    fn test_tracing_configuration_default() {
210        let config = TracingConfig::default();
211        assert_eq!(config.log_level, "info");
212        // Note: display_thread_names, display_thread_ids, and filters defaults come from core
213        // Just verify we can read them
214        let _ = config.display_thread_names;
215        let _ = config.display_thread_ids;
216        let _ = &config.filters;
217    }
218
219    #[test]
220    fn test_tracing_configuration_roundtrip() {
221        let config = TracingConfig {
222            log_level: "debug".to_string(),
223            display_thread_names: true,
224            display_thread_ids: true,
225            filters: vec!["slim=debug".to_string(), "tokio=info".to_string()],
226        };
227
228        let core: CoreTracingConfiguration = config.clone().into();
229        let back: TracingConfig = core.into();
230
231        assert_eq!(back.log_level, config.log_level);
232        assert_eq!(back.display_thread_names, config.display_thread_names);
233        assert_eq!(back.display_thread_ids, config.display_thread_ids);
234        assert_eq!(back.filters, config.filters);
235    }
236
237    #[test]
238    fn test_service_configuration_default() {
239        let config = ServiceConfig::default();
240        assert!(config.node_id.is_none());
241        assert!(config.group_name.is_none());
242    }
243
244    #[test]
245    fn test_service_configuration_roundtrip() {
246        let config = ServiceConfig {
247            node_id: Some("test-node".to_string()),
248            group_name: Some("test-group".to_string()),
249            dataplane: DataplaneConfig::default(),
250        };
251
252        let core: CoreServiceConfiguration = config.clone().into();
253        let back: ServiceConfig = core.into();
254
255        assert_eq!(back.node_id, config.node_id);
256        assert_eq!(back.group_name, config.group_name);
257    }
258
259    #[test]
260    fn test_constructor_functions() {
261        let runtime = new_runtime_config();
262        assert_eq!(runtime.n_cores, 0);
263
264        let runtime_custom =
265            new_runtime_config_with(4, "custom".to_string(), Duration::from_secs(20));
266        assert_eq!(runtime_custom.n_cores, 4);
267        assert_eq!(runtime_custom.thread_name, "custom");
268        assert_eq!(runtime_custom.drain_timeout, Duration::from_secs(20));
269
270        let tracing = new_tracing_config();
271        assert_eq!(tracing.log_level, "info");
272
273        let tracing_custom = new_tracing_config_with(
274            "debug".to_string(),
275            true,
276            true,
277            vec!["slim=debug".to_string()],
278        );
279        assert_eq!(tracing_custom.log_level, "debug");
280        assert!(tracing_custom.display_thread_names);
281
282        let service = new_service_config();
283        assert!(service.node_id.is_none());
284
285        let service_custom = new_service_config_with(
286            Some("node-1".to_string()),
287            Some("group-1".to_string()),
288            DataplaneConfig::default(),
289        );
290        assert_eq!(service_custom.node_id, Some("node-1".to_string()));
291        assert_eq!(service_custom.group_name, Some("group-1".to_string()));
292    }
293}