use aion_awl::doc::{DocumentDoc, SourceState, StepGraph, layout, svg};
use aion_proto::WireError;
use axum::{
Json,
extract::{Path, State},
http::header,
response::{IntoResponse, Response},
};
use super::auth::HttpCaller;
use super::awl::{DocumentHttpError, require_authenticated, workspace};
use super::awl_deployed::{DeployedHttpError, authorized_engine, refusal};
use super::error::HttpWireError;
use crate::ServerState;
use crate::awl::deployed::DeployedError;
use crate::awl::{self, CheckRequest, DocResponse};
const SVG_MEDIA_TYPE: &str = "image/svg+xml; charset=utf-8";
pub(crate) async fn deployed_graph_svg(
State(state): State<ServerState>,
HttpCaller(caller): HttpCaller,
Path((workflow_type, content_hash)): Path<(String, String)>,
) -> Result<Response, Response> {
let engine = authorized_engine(&state, &caller).map_err(|error| refusal(&error))?;
let doc = crate::awl::deployed::read_doc(&engine, &workflow_type, &content_hash)
.await
.map_err(|error| DeployedHttpError(error).into_response())?;
let graph = deployed_graph(doc, &workflow_type, &content_hash)
.map_err(|wire| HttpWireError(wire).into_response())?;
Ok(render(&graph))
}
pub(crate) async fn workspace_graph_svg(
State(state): State<ServerState>,
HttpCaller(caller): HttpCaller,
Json(request): Json<CheckRequest>,
) -> Result<Response, Response> {
require_authenticated(&caller)?;
let root = workspace(&state).map_err(IntoResponse::into_response)?;
let response = awl::doc_source_in_workspace(&root, &request)
.await
.map_err(|error| DocumentHttpError(error).into_response())?;
match response {
DocResponse::Derived { doc } => match doc.prose {
Some(prose) => Ok(render(&prose.graph)),
None => Err(HttpWireError(WireError::backend(
"the documentation model derived from source carried no prose, so there is no \
step graph to draw",
))
.into_response()),
},
DocResponse::Refused { reason } => Err(HttpWireError(
WireError::invalid_input(reason).with_error_type("AwlDocumentRefused"),
)
.into_response()),
}
}
fn deployed_graph(
doc: DocumentDoc,
workflow_type: &str,
content_hash: &str,
) -> Result<StepGraph, WireError> {
if let Some(prose) = doc.prose {
return Ok(prose.graph);
}
let workflow_type = workflow_type.to_owned();
let content_hash = content_hash.to_owned();
let wire = match doc.source_state {
SourceState::Absent => DeployedError::NoArchivedSource {
workflow_type,
content_hash,
}
.to_wire_error(),
SourceState::Unreadable { reason } => DeployedError::Unreadable {
workflow_type,
content_hash,
reason,
}
.to_wire_error(),
SourceState::NotPersisted => WireError::not_found_with_type(
"DeployedAwlSourceNotPersisted",
format!(
"this server holds workflow type `{workflow_type}` version `{content_hash}` but \
persists no archive for it, so its source — and its step graph — cannot be read"
),
),
SourceState::Available => WireError::backend(format!(
"the documentation model of workflow type `{workflow_type}` version `{content_hash}` \
reports its source available but carries no prose, so there is no step graph to draw"
)),
};
Err(wire)
}
fn render(graph: &StepGraph) -> Response {
let placed = layout::compute(graph);
let body = svg::render(graph, &placed);
([(header::CONTENT_TYPE, SVG_MEDIA_TYPE)], body).into_response()
}