refactor(portal): move path and query helpers into module
This commit is contained in:
@@ -25,6 +25,7 @@ use tiny_http::{Header, Method, Request, Response, Server, StatusCode};
|
|||||||
|
|
||||||
mod command_runner;
|
mod command_runner;
|
||||||
mod executive_actions;
|
mod executive_actions;
|
||||||
|
mod path_query;
|
||||||
mod portal_roles;
|
mod portal_roles;
|
||||||
mod production;
|
mod production;
|
||||||
mod risk_narrative;
|
mod risk_narrative;
|
||||||
@@ -34,6 +35,10 @@ use command_runner::run_in_dir;
|
|||||||
use executive_actions::{
|
use executive_actions::{
|
||||||
actions_from_center, build_action_center_from_report, filter_actions_for_role,
|
actions_from_center, build_action_center_from_report, filter_actions_for_role,
|
||||||
};
|
};
|
||||||
|
use path_query::{
|
||||||
|
normalize_path, parse_case_path, parse_case_status_path, parse_investigation_pack_path,
|
||||||
|
query_flag, query_param,
|
||||||
|
};
|
||||||
use portal_roles::PortalRole;
|
use portal_roles::PortalRole;
|
||||||
use production::{
|
use production::{
|
||||||
build_healthz, build_readyz, build_version, http_request_metadata, is_limited_api_route,
|
build_healthz, build_readyz, build_version, http_request_metadata, is_limited_api_route,
|
||||||
@@ -1655,16 +1660,6 @@ fn handle_evidence_only_request(request: Request, args: &Cli) -> Result<()> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_path(url: &str) -> String {
|
|
||||||
let path = url.split('?').next().unwrap_or("/");
|
|
||||||
let path = path.strip_prefix("/portal").unwrap_or(path);
|
|
||||||
if path.is_empty() {
|
|
||||||
"/".to_string()
|
|
||||||
} else {
|
|
||||||
path.to_string()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn api_contract_summary() -> Value {
|
fn api_contract_summary() -> Value {
|
||||||
json!({
|
json!({
|
||||||
"ok": true,
|
"ok": true,
|
||||||
@@ -1812,24 +1807,6 @@ fn read_json_file(path: &Path) -> Result<Value> {
|
|||||||
serde_json::from_str(&text).with_context(|| format!("parse {}", path.display()))
|
serde_json::from_str(&text).with_context(|| format!("parse {}", path.display()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn query_flag(url: &str, key: &str) -> bool {
|
|
||||||
let Some(query) = url.split_once('?').map(|(_, query)| query) else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
query.split('&').any(|pair| {
|
|
||||||
let (name, value) = pair.split_once('=').unwrap_or((pair, "1"));
|
|
||||||
name == key && matches!(value, "1" | "true" | "yes" | "on")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn query_param(url: &str, key: &str) -> Option<String> {
|
|
||||||
let query = url.split_once('?').map(|(_, query)| query)?;
|
|
||||||
query.split('&').find_map(|pair| {
|
|
||||||
let (name, value) = pair.split_once('=').unwrap_or((pair, ""));
|
|
||||||
(name == key && !value.is_empty()).then(|| value.to_string())
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn portal_role_from_request(request: &Request, url: &str) -> PortalRole {
|
fn portal_role_from_request(request: &Request, url: &str) -> PortalRole {
|
||||||
query_param(url, "role")
|
query_param(url, "role")
|
||||||
.as_deref()
|
.as_deref()
|
||||||
@@ -1869,28 +1846,6 @@ fn respond_forbidden(request: Request, role: PortalRole, scope: &str) -> Result<
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_investigation_pack_path(path: &str) -> Option<String> {
|
|
||||||
path.strip_prefix("/api/investigation-pack/")
|
|
||||||
.map(str::trim)
|
|
||||||
.filter(|value| !value.is_empty() && !value.contains('/'))
|
|
||||||
.map(ToString::to_string)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_case_path(path: &str) -> Option<String> {
|
|
||||||
path.strip_prefix("/api/cases/")
|
|
||||||
.map(str::trim)
|
|
||||||
.filter(|value| !value.is_empty() && !value.contains('/'))
|
|
||||||
.map(ToString::to_string)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn parse_case_status_path(path: &str) -> Option<String> {
|
|
||||||
path.strip_prefix("/api/cases/")
|
|
||||||
.and_then(|value| value.strip_suffix("/status"))
|
|
||||||
.map(str::trim)
|
|
||||||
.filter(|value| !value.is_empty() && !value.contains('/'))
|
|
||||||
.map(ToString::to_string)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn cached_snapshot(args: &Cli, cache: &SnapshotCache) -> Snapshot {
|
fn cached_snapshot(args: &Cli, cache: &SnapshotCache) -> Snapshot {
|
||||||
let mut guard = cache.lock().expect("snapshot cache mutex poisoned");
|
let mut guard = cache.lock().expect("snapshot cache mutex poisoned");
|
||||||
if let Some(cached) = guard.as_ref() {
|
if let Some(cached) = guard.as_ref() {
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
//! URL path and query parsing helpers for the portal.
|
||||||
|
//!
|
||||||
|
//! CONTRACT: these helpers are routing glue. Keep accepted URL shapes stable
|
||||||
|
//! because API handlers and the HTML portal depend on them.
|
||||||
|
|
||||||
|
pub(crate) fn normalize_path(url: &str) -> String {
|
||||||
|
let path = url.split('?').next().unwrap_or("/");
|
||||||
|
let path = path.strip_prefix("/portal").unwrap_or(path);
|
||||||
|
if path.is_empty() {
|
||||||
|
"/".to_string()
|
||||||
|
} else {
|
||||||
|
path.to_string()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn query_flag(url: &str, key: &str) -> bool {
|
||||||
|
let Some(query) = url.split_once('?').map(|(_, query)| query) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
query.split('&').any(|pair| {
|
||||||
|
let (name, value) = pair.split_once('=').unwrap_or((pair, "1"));
|
||||||
|
name == key && matches!(value, "1" | "true" | "yes" | "on")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn query_param(url: &str, key: &str) -> Option<String> {
|
||||||
|
let query = url.split_once('?').map(|(_, query)| query)?;
|
||||||
|
query.split('&').find_map(|pair| {
|
||||||
|
let (name, value) = pair.split_once('=').unwrap_or((pair, ""));
|
||||||
|
(name == key && !value.is_empty()).then(|| value.to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parse_investigation_pack_path(path: &str) -> Option<String> {
|
||||||
|
path.strip_prefix("/api/investigation-pack/")
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|value| !value.is_empty() && !value.contains('/'))
|
||||||
|
.map(ToString::to_string)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parse_case_path(path: &str) -> Option<String> {
|
||||||
|
path.strip_prefix("/api/cases/")
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|value| !value.is_empty() && !value.contains('/'))
|
||||||
|
.map(ToString::to_string)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn parse_case_status_path(path: &str) -> Option<String> {
|
||||||
|
path.strip_prefix("/api/cases/")
|
||||||
|
.and_then(|value| value.strip_suffix("/status"))
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|value| !value.is_empty() && !value.contains('/'))
|
||||||
|
.map(ToString::to_string)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user