use std::sync::Arc;
use axum::extract::{Path, State};
use axum::http::HeaderMap;
use axum::response::{IntoResponse, Response};
use axum::Json;
use serde::Serialize;
use crate::server::Server;
use crate::audit::Outcome;
use super::admit::{admit, refuse, served, unavailable};
use super::names::is_key_path;
#[derive(Serialize)]
struct CheckBody<'a> {
application: &'a str,
profile: &'a str,
clean: bool,
resolved: Vec<ResolvedRow>,
unknown: Vec<UnknownRow>,
failure: Option<String>,
}
#[derive(Serialize)]
struct ResolvedRow {
path: String,
origin: String,
}
#[derive(Serialize)]
struct UnknownRow {
path: String,
suggestion: Option<String>,
}
pub(super) async fn check(
State(server): State<Arc<Server>>,
headers: HeaderMap,
Path((application, profile)): Path<(String, String)>,
) -> Response {
let admitted = match admit(&server, &headers, &application, &profile, "check") {
Ok(admitted) => admitted,
Err(response) => return *response,
};
let sources = admitted.section.sources();
let report = match dynamic_config::off_thread(move || sources.check()).await {
Ok(report) => report,
Err(error) => return unavailable(&server, &admitted, &error),
};
let generation = admitted.section.generation();
served(&server, &admitted, generation);
Json(CheckBody {
application: admitted.section.application(),
profile: admitted.section.profile(),
clean: report.is_clean(),
resolved: report
.resolved
.into_iter()
.map(|resolved| ResolvedRow {
path: resolved.path,
origin: resolved.origin.to_string(),
})
.collect(),
unknown: report
.unknown
.into_iter()
.map(|unknown| UnknownRow {
path: unknown.path,
suggestion: unknown.suggestion,
})
.collect(),
failure: report.failure,
})
.into_response()
}
#[derive(Serialize)]
struct ExplainBody<'a> {
application: &'a str,
profile: &'a str,
path: String,
winner: Option<&'static str>,
rows: Vec<ExplainRow>,
}
#[derive(Serialize)]
struct ExplainRow {
layer: &'static str,
origin: Option<String>,
value: Option<String>,
}
pub(super) async fn explain(
State(server): State<Arc<Server>>,
headers: HeaderMap,
Path((application, profile, path)): Path<(String, String, String)>,
) -> Response {
let admitted = match admit(&server, &headers, &application, &profile, "explain") {
Ok(admitted) => admitted,
Err(response) => return *response,
};
if !is_key_path(&path) {
return refuse(
&server,
"explain",
Outcome::Malformed,
Some(admitted.principal.name().to_owned()),
Some((
admitted.section.application().to_owned(),
admitted.section.profile().to_owned(),
)),
);
}
let sources = admitted.section.sources();
let asked = path.clone();
let explanation = match dynamic_config::off_thread(move || sources.explain(&asked)).await {
Ok(explanation) => explanation.redacted(),
Err(error) => return unavailable(&server, &admitted, &error),
};
let generation = admitted.section.generation();
served(&server, &admitted, generation);
Json(ExplainBody {
application: admitted.section.application(),
profile: admitted.section.profile(),
path,
winner: explanation.winner().map(|row| row.layer),
rows: explanation
.rows()
.iter()
.map(|row| ExplainRow {
layer: row.layer,
origin: row.origin.as_ref().map(ToString::to_string),
value: row.value.clone(),
})
.collect(),
})
.into_response()
}