corex_api/
lib.rs

1use axum::Router;
2use std::sync::Arc;
3use tokio::net::TcpListener;
4
5/// Defines the interface for extensions that can be registered with the Core system.
6/// Extensions must implement this trait to extend the functionality of the Core system.
7pub trait ExtensionTrait: Send + Sync {
8    /// Returns the name of the extension.
9    fn name(&self) -> &'static str;
10
11    /// Extends the provided router with additional routes or middleware.
12    fn extend(&self, router: Router) -> Router;
13}
14
15/// The Core system manages the router and extensions.
16/// It allows registering extensions and running the server.
17pub struct CoreX {
18    router: Router,
19    extensions: Vec<Arc<dyn ExtensionTrait>>,
20    host: String,
21    port: u16,
22}
23
24impl CoreX {
25    /// Creates a new Core system with the specified host and port.
26    ///
27    /// # Arguments
28    /// * `host` - The host address to bind the server to (e.g., "127.0.0.1").
29    /// * `port` - The port number to bind the server to (e.g., 3000).
30    pub fn new(host: String, port: u16) -> Self {
31        Self {
32            router: Router::new(),
33            extensions: Vec::new(),
34            host,
35            port,
36        }
37    }
38
39    /// Registers an extension with the Core system.
40    ///
41    /// # Arguments
42    /// * `extension` - An `Arc<dyn ExtensionTrait>` representing the extension to register.
43    pub fn register_extension(&mut self, extension: Arc<dyn ExtensionTrait>) {
44        self.extensions.push(extension);
45    }
46
47    /// Builds the final router by applying all registered extensions.
48    pub fn build(self) -> Router {
49        let mut router = self.router;
50        for extension in self.extensions {
51            router = extension.extend(router);
52        }
53        router
54    }
55
56    /// Runs the server and starts listening for incoming requests.
57    pub async fn run(self) {
58        let addr = format!("{}:{}", self.host, self.port);
59        let router = self.build();
60        println!("Server running at http://{}", addr);
61
62        let listener = TcpListener::bind(&addr).await.unwrap();
63        axum::serve(listener, router).await.unwrap();
64    }
65}
66
67#[cfg(test)]
68mod tests {
69    use super::*;
70    use axum::{routing::get, Json};
71    use serde_json::json;
72    use tokio::io::{AsyncReadExt, AsyncWriteExt};
73    use tokio::net::TcpStream;
74
75    /// A test extension that adds a `/test` endpoint.
76    struct TestExtension;
77
78    impl ExtensionTrait for TestExtension {
79        fn name(&self) -> &'static str {
80            "TestExtension"
81        }
82
83        fn extend(&self, router: Router) -> Router {
84            router.route(
85                "/test",
86                get(|| async { Json(json!({ "message": "Test endpoint" })) }),
87            )
88        }
89    }
90
91    /// Tests the Core system with a single extension.
92    #[tokio::test]
93    async fn test_core_with_extension() {
94        let mut core = CoreX::new("127.0.0.1".to_string(), 3000);
95        core.register_extension(Arc::new(TestExtension));
96
97        // Run the server in the background
98        let handle = tokio::spawn(async move {
99            core.run().await;
100        });
101
102        // Wait for the server to start
103        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
104
105        // Test the `/test` endpoint
106        let mut stream = TcpStream::connect("127.0.0.1:3000").await.unwrap();
107        let request = "GET /test HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
108        stream.write_all(request.as_bytes()).await.unwrap();
109
110        let mut buffer = [0; 1024];
111        let n = stream.read(&mut buffer).await.unwrap();
112        let response = String::from_utf8_lossy(&buffer[..n]);
113
114        assert!(response.contains("Test endpoint"));
115
116        // Shutdown the server
117        handle.abort();
118    }
119
120    /// Tests the Core system without any extensions.
121    #[tokio::test]
122    async fn test_core_without_extensions() {
123        let core = CoreX::new("127.0.0.1".to_string(), 3001);
124
125        // Run the server in the background
126        let handle = tokio::spawn(async move {
127            core.run().await;
128        });
129
130        // Wait for the server to start
131        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
132
133        // Test the root endpoint (should return 404)
134        let mut stream = TcpStream::connect("127.0.0.1:3001").await.unwrap();
135        let request = "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
136        stream.write_all(request.as_bytes()).await.unwrap();
137
138        let mut buffer = [0; 1024];
139        let n = stream.read(&mut buffer).await.unwrap();
140        let response = String::from_utf8_lossy(&buffer[..n]);
141
142        assert!(response.contains("404 Not Found"));
143
144        // Shutdown the server
145        handle.abort();
146    }
147}