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}