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}