corex_api/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use axum::Router;
use std::sync::Arc;
use tokio::net::TcpListener;

/// Defines the interface for extensions that can be registered with the Core system.
/// Extensions must implement this trait to extend the functionality of the Core system.
pub trait ExtensionTrait: Send + Sync {
    /// Returns the name of the extension.
    fn name(&self) -> &'static str;

    /// Extends the provided router with additional routes or middleware.
    fn extend(&self, router: Router) -> Router;
}

/// The Core system manages the router and extensions.
/// It allows registering extensions and running the server.
pub struct CoreX {
    router: Router,
    extensions: Vec<Arc<dyn ExtensionTrait>>,
    host: String,
    port: u16,
}

impl CoreX {
    /// Creates a new Core system with the specified host and port.
    ///
    /// # Arguments
    /// * `host` - The host address to bind the server to (e.g., "127.0.0.1").
    /// * `port` - The port number to bind the server to (e.g., 3000).
    pub fn new(host: String, port: u16) -> Self {
        Self {
            router: Router::new(),
            extensions: Vec::new(),
            host,
            port,
        }
    }

    /// Registers an extension with the Core system.
    ///
    /// # Arguments
    /// * `extension` - An `Arc<dyn ExtensionTrait>` representing the extension to register.
    pub fn register_extension(&mut self, extension: Arc<dyn ExtensionTrait>) {
        self.extensions.push(extension);
    }

    /// Builds the final router by applying all registered extensions.
    pub fn build(self) -> Router {
        let mut router = self.router;
        for extension in self.extensions {
            router = extension.extend(router);
        }
        router
    }

    /// Runs the server and starts listening for incoming requests.
    pub async fn run(self) {
        let addr = format!("{}:{}", self.host, self.port);
        let router = self.build();
        println!("Server running at http://{}", addr);

        let listener = TcpListener::bind(&addr).await.unwrap();
        axum::serve(listener, router).await.unwrap();
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use axum::{routing::get, Json};
    use serde_json::json;
    use tokio::io::{AsyncReadExt, AsyncWriteExt};
    use tokio::net::TcpStream;

    /// A test extension that adds a `/test` endpoint.
    struct TestExtension;

    impl ExtensionTrait for TestExtension {
        fn name(&self) -> &'static str {
            "TestExtension"
        }

        fn extend(&self, router: Router) -> Router {
            router.route(
                "/test",
                get(|| async { Json(json!({ "message": "Test endpoint" })) }),
            )
        }
    }

    /// Tests the Core system with a single extension.
    #[tokio::test]
    async fn test_core_with_extension() {
        let mut core = CoreX::new("127.0.0.1".to_string(), 3000);
        core.register_extension(Arc::new(TestExtension));

        // Run the server in the background
        let handle = tokio::spawn(async move {
            core.run().await;
        });

        // Wait for the server to start
        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;

        // Test the `/test` endpoint
        let mut stream = TcpStream::connect("127.0.0.1:3000").await.unwrap();
        let request = "GET /test HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
        stream.write_all(request.as_bytes()).await.unwrap();

        let mut buffer = [0; 1024];
        let n = stream.read(&mut buffer).await.unwrap();
        let response = String::from_utf8_lossy(&buffer[..n]);

        assert!(response.contains("Test endpoint"));

        // Shutdown the server
        handle.abort();
    }

    /// Tests the Core system without any extensions.
    #[tokio::test]
    async fn test_core_without_extensions() {
        let core = CoreX::new("127.0.0.1".to_string(), 3001);

        // Run the server in the background
        let handle = tokio::spawn(async move {
            core.run().await;
        });

        // Wait for the server to start
        tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;

        // Test the root endpoint (should return 404)
        let mut stream = TcpStream::connect("127.0.0.1:3001").await.unwrap();
        let request = "GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
        stream.write_all(request.as_bytes()).await.unwrap();

        let mut buffer = [0; 1024];
        let n = stream.read(&mut buffer).await.unwrap();
        let response = String::from_utf8_lossy(&buffer[..n]);

        assert!(response.contains("404 Not Found"));

        // Shutdown the server
        handle.abort();
    }
}