rusty_api/
api.rs

1/*!
2 * The `api` module provides the core functionality for setting up and running a secure, high-performance API server with rusty-api.
3 *
4 * This module integrates with the Actix Web framework and includes features such as:
5 * - **TLS Support**: Secure communication using Rustls.
6 * - **Rate Limiting**: Configurable request limits to prevent abuse.
7 * - **CORS Configuration**: Flexible settings for managing cross-origin requests.
8 * - **Custom Routes**: Easily define and configure API routes/endpoints.
9 *
10 * The `Api` struct serves as the main entry point for configuring and starting the server, offering methods for setting
11 * up TLS, binding to an address, configuring routes, and more.
12 */
13use crate::core::config::load_rustls_config;
14use crate::routes::Routes;
15
16use actix_web::{App, HttpServer, web};
17use actix_governor::{Governor, GovernorConfigBuilder};
18use actix_cors::Cors;
19use std::sync::Arc;
20
21/**
22 * The `Api` struct is the main entry point for configuring and running the API server.
23 *
24 * This struct provides methods to set up TLS, configure routes, manage CORS settings, 
25 * apply rate limiting, and bind the server to a specific address and port. It is designed 
26 * to simplify the process of building secure and high-performance APIs using the Actix Web framework.
27 *
28 * # Features
29 * - **TLS Support**: Configure paths to certificate and key files for secure HTTPS communication.
30 * - **Rate Limiting**: Set request limits to prevent abuse.
31 * - **CORS Configuration**: Customize cross-origin resource sharing settings.
32 * - **Custom Routes**: Define and configure API routes using the `Routes` builder.
33 *
34 * # Example
35 * ```rust,no_run
36 * use rusty_api::Api;
37 *
38 * let api = Api::new()
39 *      .certs("certs/cert.pem", "certs/key.pem")
40 *      .rate_limit(5, 10)
41 *      .bind("127.0.0.1", 8443)
42 *      .configure_cors(|| {
43 *          rusty_api::Cors::default()
44 *              .allow_any_origin()
45 *              .allow_any_method()
46 *      });
47 * 
48 * api.start();
49 * ```
50 */
51pub struct Api {
52    /// Path to the TLS certificate file used for secure HTTPS communication.
53    cert_path: String,
54
55    /// Path to the private key used for TLS.
56    key_path: String,
57
58    /// Address to bind the API server to (e.g., "127.0.0.1").
59    addr: String,
60    
61    /// Port to bind the API server to (e.g., 8443).
62    port: u16,
63
64    /// Rate limiting configuration: `(requests_per_second, burst_size)`.
65    rate_limit: (u64, u32),
66
67    /// Optional custom routes configuration, provided as a closure.
68    custom_routes: Option<Arc<dyn Fn(&mut web::ServiceConfig) + Send + Sync>>,
69
70    /// Custom CORS configuration, provided as a closure.
71    custom_cors: Arc<dyn Fn() -> Cors + Send + Sync>,
72}
73
74impl Api {
75    /**
76     * Create a new instance of the API server with default settings.
77     *
78     * # Returns
79     * A new `Api` instance with default values.
80     *
81     * # Example
82     * ```rust
83     * use rusty_api::Api;
84     *
85     * let api = Api::new();
86     * assert_eq!(api.get_cert_path(), "certs/cert.pem");
87     * assert_eq!(api.get_key_path(), "certs/key.pem");
88     * assert_eq!(api.get_addr(), "127.0.0.1");
89     * assert_eq!(api.get_port(), 8443);
90     * assert_eq!(api.get_rate_limit(), (3, 20));
91     * ```
92     */
93    pub fn new() -> Self {
94        Self {
95            cert_path: "certs/cert.pem".into(),
96            key_path: "certs/key.pem".into(),
97            addr: "127.0.0.1".into(),
98            port: 8443,
99            rate_limit: (3, 20),
100            custom_routes: None,
101            custom_cors: Arc::new(|| Cors::default()),
102        }
103    }
104
105    /**
106     * Set the certificate and key paths for TLS.
107     *
108     * # Arguments
109     * * `cert` - Path to the certificate file.
110     * * `key` - Path to the private key file.
111     *
112     * # Returns
113     * A mutable reference to the `Api` instance.
114     *
115     * # Example
116     * ```rust
117     * use rusty_api::Api;
118     *
119     * let api = Api::new().certs("path/to/cert.pem", "path/to/key.pem");
120     * assert_eq!(api.get_cert_path(), "path/to/cert.pem");
121     * assert_eq!(api.get_key_path(), "path/to/key.pem");
122     * ```
123     */
124    pub fn certs(mut self, cert: &str, key: &str) -> Self {
125        self.cert_path = cert.into();
126        self.key_path = key.into();
127        self
128    }
129
130    /**
131     * Set the rate limit for API requests.
132     *
133     * # Arguments
134     * * `per_second` - Number of requests allowed per second.
135     * * `burst_size` - Maximum burst size for requests.
136     *
137     * # Returns
138     * A mutable reference to the `Api` instance.
139     *
140     * # Example
141     * ```
142     * use rusty_api::Api;
143     *
144     * let api = Api::new().rate_limit(5, 10);
145     * assert_eq!(api.get_rate_limit_per_second(), 5);
146     * assert_eq!(api.get_rate_limit_burst_size(), 10);
147     * ```
148     */
149    pub fn rate_limit(mut self, per_second: u64, burst_size: u32) -> Self {
150        self.rate_limit = (per_second, burst_size);
151        self
152    }
153
154    /**
155     * Set the address and port for the API server.
156     *
157     * # Arguments
158     * * `addr` - Address to bind the server to.
159     * * `port` - Port to bind the server to.
160     *
161     * # Returns
162     * A mutable reference to the `Api` instance.
163     *
164     * # Example
165     * ```rust
166     * use rusty_api::Api;
167     *
168     * let api = Api::new().bind("127.0.0.1", 8443);
169     * assert_eq!(api.get_bind_addr(), "127.0.0.1:8443");
170     * ```
171     */
172    pub fn bind(mut self, addr: &str, port: u16) -> Self {
173        self.addr = addr.into();
174        self.port = port;
175        self
176    }
177
178    /**
179     * Configure custom routes for the API server.
180     *
181     * # Arguments
182     * * `routes` - A `Routes` instance containing the custom routes.
183     * 
184     * # Returns
185     * A mutable reference to the `Api` instance.
186     *
187     * # Example
188     * ```rust
189     * use rusty_api::Api;
190     * use rusty_api::Routes;
191     *
192     * async fn example_route(_req: rusty_api::HttpRequest) -> rusty_api::HttpResponse {
193     *   rusty_api::HttpResponse::Ok().body("Example route accessed!")
194     * }
195     *
196     * let routes = Routes::new()
197     *    .add_route("/example", example_route);
198     *
199     * let api = Api::new()
200     *    .configure_routes(routes);
201     *
202     * assert!(api.get_custom_routes().is_some());
203     * ```
204     */
205    pub fn configure_routes(mut self, routes: Routes) -> Self {
206        self.custom_routes = Some(Arc::new(move |cfg| routes.configure(cfg)));
207        self
208    }
209
210    /**
211     * Configure CORS settings.
212     *
213     * # Arguments
214     * * `cors` - A closure that takes a `Cors` instance and returns a modified `Cors` instance.
215     *
216     * # Returns
217     * A mutable reference to the `Api` instance.
218     *
219     * # Example
220     * ```rust
221     * use rusty_api;
222     *
223     * let api = rusty_api::Api::new()
224     *     .configure_cors(|| {
225     *         rusty_api::Cors::default()
226     *             .allow_any_origin()
227     *             .allow_any_method()
228     *     });
229     * ```
230     */
231    pub fn configure_cors<F>(mut self, cors_config: F) -> Self
232    where
233        F: Fn() -> Cors + Send + Sync + 'static,
234    {
235        self.custom_cors = Arc::new(cors_config);
236        self
237    }
238
239    /**
240     * Start the API server.
241     * 
242     * This method initializes the server and begins listening for incoming requests.
243     * It will block the current thread until the server is stopped.
244     *
245     * # Example
246     * ```rust,no_run
247     * use rusty_api::Api;
248     *
249     * let api = Api::new().start();
250     */
251    pub fn start(self) {
252        let rt = actix_web::rt::System::new();
253        if let Err(e) = rt.block_on(async {
254            println!("INFO: Starting API server...");
255
256            let tls_config = load_rustls_config(&self.cert_path, &self.key_path).expect("TLS failed");
257
258            let governor_config = GovernorConfigBuilder::default()
259                .per_second(self.rate_limit.0)
260                .burst_size(self.rate_limit.1)
261                .finish()
262                .unwrap();
263
264            let cors_config = self.custom_cors.clone();
265
266            let bind_addr = format!("{}:{}", self.addr, self.port);
267
268            println!("INFO: Server binding to {}", bind_addr);
269            HttpServer::new(move || {
270                let cors = (cors_config)();
271                let mut app = App::new()
272                    .wrap(cors)
273                    .wrap(Governor::new(&governor_config));
274
275                // Apply custom routes if provided
276                if let Some(custom_routes) = &self.custom_routes {
277                    app = app.configure(|cfg| custom_routes(cfg));
278                }
279
280                app
281            })
282            .bind_rustls_0_23((self.addr.to_string(), self.port), tls_config)?
283            .run()
284            .await
285        }) {
286            println!("ERROR: Failed to start API server: {:?}", e);
287        }
288    }
289
290    /**
291     * Get the path to the TLS certificate file.
292     *
293     * # Returns
294     * A string representing the path to the certificate file.
295     *
296     * # Example
297     * ```rust
298     * use rusty_api::Api;
299     *
300     * let api = Api::new().certs("path/to/cert.pem", "path/to/key.pem");
301     * assert_eq!(api.get_cert_path(), "path/to/cert.pem");
302     * ```
303     */
304    pub fn get_cert_path(&self) -> &str { &self.cert_path }
305
306    /**
307     * Get the path to the TLS private key file.
308     *
309     * # Returns
310     * A string representing the path to the private key file.
311     *
312     * # Example
313     * ```rust
314     * use rusty_api::Api;
315     *
316     * let api = Api::new().certs("path/to/cert.pem", "path/to/key.pem");
317     * assert_eq!(api.get_key_path(), "path/to/key.pem");
318     * ```
319     */
320    pub fn get_key_path(&self) -> &str { &self.key_path }
321
322    /**
323     * Get the address the server is bound to.
324     *
325     * # Returns
326     * A string representing the address.
327     *
328     * # Example
329     * ```rust
330     * use rusty_api::Api;
331     * 
332     * let api = Api::new().bind("123.4.5.6", 7891);
333     * assert_eq!(api.get_addr(), "123.4.5.6");
334     * ```
335     */
336    pub fn get_addr(&self) -> &str { &self.addr }
337
338    /**
339     * Get the port the server is bound to.
340     *
341     * # Returns
342     * A u16 representing the port.
343     * 
344     * # Example
345     * ```rust
346     * use rusty_api::Api;
347     * 
348     * let api = Api::new().bind("123.4.5.6", 7891);
349     * assert_eq!(api.get_port(), 7891);
350     * ```
351     */
352    pub fn get_port(&self) -> u16 { self.port }
353
354    /**
355     * Get the rate limit configuration.
356     *
357     * # Returns
358     * A tuple containing the rate limit configuration: `(requests_per_second, burst_size)`.
359     *
360     * # Example
361     * ```rust
362     * use rusty_api::Api;
363     *
364     * let api = Api::new().rate_limit(5, 10);
365     * assert_eq!(api.get_rate_limit(), (5, 10));
366     * ```
367     */
368    pub fn get_rate_limit(&self) -> (u64, u32) { self.rate_limit }
369
370    /**
371     * Get the address and port the server is bound to as a single string.
372     *
373     * # Returns
374     * A string representing the address and port in the format "address:port".
375     * 
376     * # Example
377     * ```rust
378     * use rusty_api::Api;
379     *
380     * let api = Api::new().bind("123.4.5.6", 7891);
381     * assert_eq!(api.get_bind_addr(), "123.4.5.6:7891");
382     * ```
383     */
384    pub fn get_bind_addr(&self) -> String { format!("{}:{}", self.addr, self.port) }
385
386    /**
387     * Get the rate limit per second.
388     *
389     * # Returns
390     * A u64 representing the number of requests allowed per second.
391     *
392     * # Example
393     * ```rust
394     * use rusty_api::Api;
395     *
396     * let api = Api::new().rate_limit(5, 10);
397     * assert_eq!(api.get_rate_limit_per_second(), 5);
398     * ```
399     */
400    pub fn get_rate_limit_per_second(&self) -> u64 { self.rate_limit.0 }
401
402    /**
403     * Get the rate limit burst size.
404     *
405     * # Returns
406     * A u32 representing the maximum burst size for requests.
407     *
408     * # Example
409     * ```rust
410     * use rusty_api::Api;
411     *
412     * let api = Api::new().rate_limit(5, 10);
413     * assert_eq!(api.get_rate_limit_burst_size(), 10);
414     * ```
415     */
416    pub fn get_rate_limit_burst_size(&self) -> u32 { self.rate_limit.1 }
417
418    /**
419     * Get the custom CORS configuration.
420     *
421     * # Returns
422     * A reference to the custom CORS configuration closure.
423     */
424    pub fn get_custom_routes(&self) -> Option<&Arc<dyn Fn(&mut web::ServiceConfig) + Send + Sync>> { self.custom_routes.as_ref() }
425}