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