allframe_core/router/
config.rs

1//! Configuration-driven protocol selection
2//!
3//! This module enables AllFrame's key differentiator: write handlers once,
4//! expose them via multiple protocols through configuration alone.
5
6use serde::{Deserialize, Serialize};
7
8/// Router configuration with protocol selection
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct RouterConfig {
11    /// Server configuration
12    pub server: ServerConfig,
13}
14
15/// Server configuration
16#[derive(Debug, Clone, Serialize, Deserialize)]
17pub struct ServerConfig {
18    /// Enabled protocols
19    pub protocols: Vec<String>,
20
21    /// REST-specific configuration
22    #[serde(default, skip_serializing_if = "Option::is_none")]
23    pub rest: Option<RestConfig>,
24
25    /// GraphQL-specific configuration
26    #[serde(default, skip_serializing_if = "Option::is_none")]
27    pub graphql: Option<GraphQLConfig>,
28
29    /// gRPC-specific configuration
30    #[serde(default, skip_serializing_if = "Option::is_none")]
31    pub grpc: Option<GrpcConfig>,
32}
33
34/// REST protocol configuration
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct RestConfig {
37    /// Port to listen on
38    #[serde(default = "default_rest_port")]
39    pub port: u16,
40
41    /// Path prefix for all REST endpoints
42    #[serde(default = "default_rest_prefix")]
43    pub path_prefix: String,
44}
45
46fn default_rest_port() -> u16 {
47    8080
48}
49
50fn default_rest_prefix() -> String {
51    "/api/v1".to_string()
52}
53
54/// GraphQL protocol configuration
55#[derive(Debug, Clone, Serialize, Deserialize)]
56pub struct GraphQLConfig {
57    /// Port to listen on
58    #[serde(default = "default_graphql_port")]
59    pub port: u16,
60
61    /// GraphQL endpoint path
62    #[serde(default = "default_graphql_path")]
63    pub path: String,
64
65    /// Enable GraphiQL playground
66    #[serde(default)]
67    pub playground: bool,
68}
69
70fn default_graphql_port() -> u16 {
71    8081
72}
73
74fn default_graphql_path() -> String {
75    "/graphql".to_string()
76}
77
78/// gRPC protocol configuration
79#[derive(Debug, Clone, Serialize, Deserialize)]
80pub struct GrpcConfig {
81    /// Port to listen on
82    #[serde(default = "default_grpc_port")]
83    pub port: u16,
84
85    /// Enable reflection API
86    #[serde(default)]
87    pub reflection: bool,
88}
89
90fn default_grpc_port() -> u16 {
91    9090
92}
93
94impl RouterConfig {
95    /// Parse configuration from TOML string
96    pub fn from_toml(toml: &str) -> Result<Self, String> {
97        toml::from_str(toml).map_err(|e| format!("Failed to parse config: {}", e))
98    }
99
100    /// Parse configuration from TOML file
101    pub fn from_file(path: &str) -> Result<Self, String> {
102        let contents =
103            std::fs::read_to_string(path).map_err(|e| format!("Failed to read file: {}", e))?;
104        Self::from_toml(&contents)
105    }
106
107    /// Get enabled protocols
108    pub fn protocols(&self) -> &[String] {
109        &self.server.protocols
110    }
111
112    /// Check if a protocol is enabled
113    pub fn has_protocol(&self, protocol: &str) -> bool {
114        self.server.protocols.contains(&protocol.to_string())
115    }
116
117    /// Get REST configuration
118    pub fn rest(&self) -> Option<&RestConfig> {
119        self.server.rest.as_ref()
120    }
121
122    /// Get GraphQL configuration
123    pub fn graphql(&self) -> Option<&GraphQLConfig> {
124        self.server.graphql.as_ref()
125    }
126
127    /// Get gRPC configuration
128    pub fn grpc(&self) -> Option<&GrpcConfig> {
129        self.server.grpc.as_ref()
130    }
131}
132
133impl RestConfig {
134    /// Get the port
135    pub fn port(&self) -> u16 {
136        self.port
137    }
138
139    /// Get the path prefix
140    pub fn path_prefix(&self) -> &str {
141        &self.path_prefix
142    }
143}
144
145impl GraphQLConfig {
146    /// Get the port
147    pub fn port(&self) -> u16 {
148        self.port
149    }
150
151    /// Get the path
152    pub fn path(&self) -> &str {
153        &self.path
154    }
155
156    /// Check if playground is enabled
157    pub fn playground(&self) -> bool {
158        self.playground
159    }
160}
161
162impl GrpcConfig {
163    /// Get the port
164    pub fn port(&self) -> u16 {
165        self.port
166    }
167
168    /// Check if reflection is enabled
169    pub fn reflection(&self) -> bool {
170        self.reflection
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn test_parse_basic_config() {
180        let toml = r#"
181            [server]
182            protocols = ["rest", "graphql"]
183        "#;
184
185        let config = RouterConfig::from_toml(toml).unwrap();
186        assert_eq!(config.protocols().len(), 2);
187        assert!(config.has_protocol("rest"));
188        assert!(config.has_protocol("graphql"));
189        assert!(!config.has_protocol("grpc"));
190    }
191
192    #[test]
193    fn test_parse_full_config() {
194        let toml = r#"
195            [server]
196            protocols = ["rest", "graphql", "grpc"]
197
198            [server.rest]
199            port = 8080
200            path_prefix = "/api/v1"
201
202            [server.graphql]
203            port = 8081
204            path = "/graphql"
205            playground = true
206
207            [server.grpc]
208            port = 9090
209            reflection = true
210        "#;
211
212        let config = RouterConfig::from_toml(toml).unwrap();
213
214        // Check protocols
215        assert_eq!(config.protocols().len(), 3);
216        assert!(config.has_protocol("rest"));
217        assert!(config.has_protocol("graphql"));
218        assert!(config.has_protocol("grpc"));
219
220        // Check REST config
221        let rest = config.rest().unwrap();
222        assert_eq!(rest.port(), 8080);
223        assert_eq!(rest.path_prefix(), "/api/v1");
224
225        // Check GraphQL config
226        let graphql = config.graphql().unwrap();
227        assert_eq!(graphql.port(), 8081);
228        assert_eq!(graphql.path(), "/graphql");
229        assert!(graphql.playground());
230
231        // Check gRPC config
232        let grpc = config.grpc().unwrap();
233        assert_eq!(grpc.port(), 9090);
234        assert!(grpc.reflection());
235    }
236
237    #[test]
238    fn test_parse_minimal_config() {
239        let toml = r#"
240            [server]
241            protocols = ["rest"]
242        "#;
243
244        let config = RouterConfig::from_toml(toml).unwrap();
245        assert_eq!(config.protocols().len(), 1);
246        assert!(config.has_protocol("rest"));
247        assert!(config.rest().is_none()); // No explicit REST config
248    }
249
250    #[test]
251    fn test_default_values() {
252        let toml = r#"
253            [server]
254            protocols = ["rest", "graphql", "grpc"]
255
256            [server.rest]
257            [server.graphql]
258            [server.grpc]
259        "#;
260
261        let config = RouterConfig::from_toml(toml).unwrap();
262
263        // Check default REST values
264        let rest = config.rest().unwrap();
265        assert_eq!(rest.port(), 8080);
266        assert_eq!(rest.path_prefix(), "/api/v1");
267
268        // Check default GraphQL values
269        let graphql = config.graphql().unwrap();
270        assert_eq!(graphql.port(), 8081);
271        assert_eq!(graphql.path(), "/graphql");
272        assert!(!graphql.playground()); // Default is false
273
274        // Check default gRPC values
275        let grpc = config.grpc().unwrap();
276        assert_eq!(grpc.port(), 9090);
277        assert!(!grpc.reflection()); // Default is false
278    }
279}