use axum::http::Method;
use serde_json::{Map, Value, json};
use super::{
METHOD_ACTIVITY, METHOD_ACTIVITY_STREAM, METHOD_ADMIN_STOP, METHOD_CONFIG, METHOD_DRAWERS_LIST,
METHOD_DREAM_RUN, METHOD_DREAM_STATUS, METHOD_HEALTH, METHOD_KG_ALL, METHOD_KG_COUNT,
METHOD_KG_GRAPH, METHOD_KG_GRAPH_NEIGHBORS, METHOD_KG_GRAPH_SEED, METHOD_KG_QUERY_TOOL,
METHOD_KG_SUBJECTS_WITH_COUNTS, METHOD_LOGS_TAIL, METHOD_PALACE_GET, METHOD_PALACES_LIST,
METHOD_STATUS,
};
#[derive(Debug, PartialEq, Eq)]
pub(crate) enum Call {
Unary {
method: &'static str,
params: Value,
},
Stream {
method: &'static str,
params: Value,
},
}
pub(crate) fn map_request(
method: &Method,
path: &str,
query: Option<&str>,
) -> Result<Call, String> {
let segments: Vec<&str> = path.trim_matches('/').split('/').collect();
let q = query_json(query);
let unary = |method: &'static str, params: Value| Ok(Call::Unary { method, params });
let stream = |method: &'static str, params: Value| Ok(Call::Stream { method, params });
match (method, segments.as_slice()) {
(&Method::GET, ["health"]) => unary(METHOD_HEALTH, json!({})),
(&Method::GET, ["api", "v1", "status"]) => unary(METHOD_STATUS, json!({})),
(&Method::GET, ["api", "v1", "config"]) => unary(METHOD_CONFIG, json!({})),
(&Method::GET, ["api", "v1", "logs", "tail"]) => unary(METHOD_LOGS_TAIL, q),
(&Method::GET, ["api", "v1", "activity"]) => unary(METHOD_ACTIVITY, q),
(&Method::GET, ["api", "v1", "dream", "status"]) => unary(METHOD_DREAM_STATUS, json!({})),
(&Method::POST, ["api", "v1", "dream", "run"]) => unary(METHOD_DREAM_RUN, json!({})),
(&Method::POST, ["api", "v1", "admin", "stop"]) => unary(METHOD_ADMIN_STOP, json!({})),
(&Method::GET, ["api", "v1", "palaces"]) => unary(METHOD_PALACES_LIST, q),
(&Method::GET, ["api", "v1", "palaces", id]) => {
unary(METHOD_PALACE_GET, json!({ "palace_id": id }))
}
(&Method::GET, ["api", "v1", "palaces", id, "drawers"]) => {
unary(METHOD_DRAWERS_LIST, with_palace(id, q))
}
(&Method::GET, ["api", "v1", "palaces", id, "kg"]) => {
let subject = query_json(query)
.get("subject")
.and_then(Value::as_str)
.map(str::to_owned)
.ok_or_else(|| {
format!("GET /api/v1/palaces/{id}/kg needs a `subject` query parameter")
})?;
unary(
METHOD_KG_QUERY_TOOL,
json!({ "palace": id, "subject": subject }),
)
}
(&Method::GET, ["api", "v1", "palaces", id, "kg", "subjects_with_counts"]) => {
unary(METHOD_KG_SUBJECTS_WITH_COUNTS, with_palace(id, q))
}
(&Method::GET, ["api", "v1", "palaces", id, "kg", "all"]) => {
unary(METHOD_KG_ALL, with_palace(id, q))
}
(&Method::GET, ["api", "v1", "palaces", id, "kg", "count"]) => {
unary(METHOD_KG_COUNT, json!({ "palace_id": id }))
}
(&Method::GET, ["api", "v1", "palaces", id, "kg", "graph"]) => {
unary(METHOD_KG_GRAPH, json!({ "palace_id": id }))
}
(&Method::GET, ["api", "v1", "palaces", id, "kg", "graph", "seed"]) => {
unary(METHOD_KG_GRAPH_SEED, with_palace(id, q))
}
(&Method::GET, ["api", "v1", "palaces", id, "kg", "graph", "neighbors"]) => {
unary(METHOD_KG_GRAPH_NEIGHBORS, with_palace(id, q))
}
(&Method::GET, ["sse"]) => stream(METHOD_ACTIVITY_STREAM, json!({})),
_ => Err(format!(
"no trusty-memory socket method serves {method} /{}. The console reaches \
trusty-memory over its Unix socket (ADR-0032, #6286); only the endpoints the \
dashboard uses are mapped.",
path.trim_matches('/')
)),
}
}
fn with_palace(id: &str, mut params: Value) -> Value {
if let Some(obj) = params.as_object_mut() {
obj.insert("palace_id".to_string(), Value::String(id.to_string()));
}
params
}
fn query_json(query: Option<&str>) -> Value {
let mut out = Map::new();
let Some(raw) = query.filter(|q| !q.is_empty()) else {
return Value::Object(out);
};
let Ok(url) = reqwest::Url::parse(&format!("http://console/?{raw}")) else {
return Value::Object(out);
};
for (key, value) in url.query_pairs() {
let coerced = match value.as_ref() {
"true" => Value::Bool(true),
"false" => Value::Bool(false),
other => match other.parse::<i64>() {
Ok(n) => Value::from(n),
Err(_) => Value::String(other.to_string()),
},
};
out.insert(key.into_owned(), coerced);
}
Value::Object(out)
}