5f70d69
commit 5f70d69
5 files changed
+30 -27
Showing 5 changed files+30-27
βΌcrates/justapi-cli/src/main.rs
+3-6
| @@ -372,7 +372,7 @@ fn prompt_api_type_selection(name: &str) -> String { | ||
372 | 372 | fn prompt_db_selection(name: &str) -> (String, String) { |
373 | 373 | use std::io::{IsTerminal, Write}; |
374 | 374 | if std::io::stdin().is_terminal() && std::io::stdout().is_terminal() { |
375 | - println!("π Welcome to JustAPI Project Scaffolder!"); | |
375 | + println!("Welcome to JustAPI Project Scaffolder!"); | |
376 | 376 | println!("Select a database backend for '{name}':"); |
377 | 377 | println!(" Relational (Transactional SQL / OLTP):"); |
378 | 378 | println!(" [1] SQLite (Zero-config, embedded file database) [default]"); |
| @@ -1088,10 +1088,7 @@ fn scaffold_project( | ||
1088 | 1088 | ), |
1089 | 1089 | )?; |
1090 | 1090 | |
1091 | - println!( | |
1092 | - "β¨ Created new JustAPI project '{}' (API: {}, database: {})", | |
1093 | - name, api_type, db_kind | |
1094 | - ); | |
1091 | + println!("Created new JustAPI project '{}' (API: {}, database: {})", name, api_type, db_kind); | |
1095 | 1092 | println!(" ββ OpenTelemetry observability compose file: docker-compose.otel.yml"); |
1096 | 1093 | println!(); |
1097 | 1094 | println!(" cd {}", project_dir.display()); |
| @@ -1762,7 +1759,7 @@ async fn run() -> anyhow::Result<()> { | ||
1762 | 1759 | } |
1763 | 1760 | |
1764 | 1761 | Commands::Profile { addr, duration, connections, output } => { |
1765 | - println!("π¬ JustAPI Profiler"); | |
1762 | + println!("JustAPI Profiler"); | |
1766 | 1763 | println!(" Target: {addr}"); |
1767 | 1764 | println!(" Duration: {duration}s"); |
1768 | 1765 | println!(" Connections: {connections}"); |
1769 | 1766 | |
βΌcrates/justapi-core/src/middleware.rs
+25-16
| @@ -384,27 +384,35 @@ impl<B: Send + 'static> Middleware<B> for SecurityHeaders { | ||
384 | 384 | let mut resp = next.run(req).await?; |
385 | 385 | let headers = resp.headers_mut(); |
386 | 386 | |
387 | - headers.insert("x-content-type-options", "nosniff".parse().unwrap()); | |
387 | + headers.insert("x-content-type-options", HeaderValue::from_static("nosniff")); | |
388 | 388 | |
389 | 389 | if self.include_xfo { |
390 | - headers.insert("x-frame-options", "DENY".parse().unwrap()); | |
390 | + headers.insert("x-frame-options", HeaderValue::from_static("DENY")); | |
391 | 391 | } |
392 | 392 | |
393 | - let hsts_val = if self.include_hsts_preload { | |
394 | - format!("{}; preload", self.hsts.trim_end_matches("; preload")) | |
395 | - } else { | |
396 | - self.hsts.clone() | |
397 | - }; | |
398 | 393 | if self.include_hsts { |
399 | - headers.insert("strict-transport-security", hsts_val.parse().unwrap()); | |
394 | + let hsts_val = if self.include_hsts_preload { | |
395 | + format!("{}; preload", self.hsts.trim_end_matches("; preload")) | |
396 | + } else { | |
397 | + self.hsts.clone() | |
398 | + }; | |
399 | + headers.insert( | |
400 | + "strict-transport-security", | |
401 | + HeaderValue::from_str(&hsts_val) | |
402 | + .map_err(|e| anyhow::anyhow!("invalid HSTS value: {e}"))?, | |
403 | + ); | |
400 | 404 | } |
401 | 405 | |
402 | 406 | if self.include_csp { |
403 | 407 | let csp = self.csp_directives.join("; "); |
404 | - headers.insert("content-security-policy", csp.parse().unwrap()); | |
408 | + headers.insert( | |
409 | + "content-security-policy", | |
410 | + HeaderValue::from_str(&csp) | |
411 | + .map_err(|e| anyhow::anyhow!("invalid CSP value: {e}"))?, | |
412 | + ); | |
405 | 413 | } |
406 | 414 | |
407 | - headers.insert("x-xss-protection", "0".parse().unwrap()); | |
415 | + headers.insert("x-xss-protection", HeaderValue::from_static("0")); | |
408 | 416 | Ok(resp) |
409 | 417 | } |
410 | 418 | } |
| @@ -662,9 +670,10 @@ pub struct RateLimiter { | ||
662 | 670 | |
663 | 671 | impl RateLimiter { |
664 | 672 | pub fn new(duration: std::time::Duration, max_burst: u32) -> Self { |
673 | + let burst = NonZeroU32::new(max_burst).unwrap_or(NonZeroU32::MIN); | |
665 | 674 | let quota = governor::Quota::with_period(duration) |
666 | - .unwrap() | |
667 | - .allow_burst(NonZeroU32::new(max_burst).unwrap()); | |
675 | + .expect("valid rate-limit duration") | |
676 | + .allow_burst(burst); | |
668 | 677 | Self { limiter: governor::RateLimiter::direct(quota) } |
669 | 678 | } |
670 | 679 | |
| @@ -747,10 +756,10 @@ impl<B: Send + 'static> Middleware<B> for IpRateLimiter { | ||
747 | 756 | let wait = negative.wait_time_from(now); |
748 | 757 | let retry_after_secs = wait.as_secs().max(1); |
749 | 758 | let mut resp = json_error(StatusCode::TOO_MANY_REQUESTS, "rate limit exceeded"); |
750 | - resp.headers_mut() | |
751 | - .insert("retry-after", retry_after_secs.to_string().parse().unwrap()); | |
752 | - resp.headers_mut() | |
753 | - .insert("x-ratelimit-reset", retry_after_secs.to_string().parse().unwrap()); | |
759 | + let retry_val = HeaderValue::from_str(&retry_after_secs.to_string()) | |
760 | + .map_err(|e| anyhow::anyhow!("invalid retry-after: {e}"))?; | |
761 | + resp.headers_mut().insert("retry-after", retry_val.clone()); | |
762 | + resp.headers_mut().insert("x-ratelimit-reset", retry_val); | |
754 | 763 | Ok(resp) |
755 | 764 | } |
756 | 765 | } |
757 | 766 | |
βΌcrates/justapi-core/src/server/mod.rs
+1-1
| @@ -958,7 +958,7 @@ impl Server { | ||
958 | 958 | return res; |
959 | 959 | } |
960 | 960 | |
961 | - tracing::info!("π JustAPI serving on http://{}", local_addr); | |
961 | + tracing::info!("JustAPI serving on http://{}", local_addr); | |
962 | 962 | tracing::info!(" ready β endpoints live at http://{}/", local_addr); |
963 | 963 | #[cfg(feature = "ws")] |
964 | 964 | let res = serve_http( |
965 | 965 | |
βΌcrates/justapi-py/python/justapi/cli.py
+1-1
| @@ -12,7 +12,7 @@ def main(): | ||
12 | 12 | print("justapi 2.0.0") |
13 | 13 | return |
14 | 14 | |
15 | - print("π JustAPI Runtime v2.0.0 (Python package)") | |
15 | + print("JustAPI Runtime v2.0.0 (Python package)") | |
16 | 16 | print("Commands:") |
17 | 17 | print(" justapi --version Display version") |
18 | 18 | print(" python -m app.main Run application directly") |
19 | 19 | |
βΌcrates/justapi-py/src/request.rs
+0-3
| @@ -198,9 +198,6 @@ impl Request { | ||
198 | 198 | auth_claims: Option<String>, |
199 | 199 | ) -> Self { |
200 | 200 | let state = PyDict::new(py); |
201 | - // Bridge JWT claims from the Rust middleware into request.state["auth"]. | |
202 | - // The `auth_claims` is a JSON string of the decoded JWT payload, set by | |
203 | - // the middleware and forwarded through `call_python_handler`. | |
204 | 201 | if let Some(claims_json) = &auth_claims { |
205 | 202 | if let Ok(serde_json::Value::Object(map)) = |
206 | 203 | serde_json::from_str::<serde_json::Value>(claims_json) |
207 | 204 | |