Skip to main content

topcoat_runtime/layer/
rerun.rs

1//! Renders a page again when the browser sends a runtime POST to its URL.
2
3use serde::Deserialize;
4use topcoat_core::{context::Cx, error::Result};
5use topcoat_router::{
6    Body, HeaderName, HeaderValue, Method,
7    content::Json,
8    error::rewrite,
9    header,
10    request::{FromRequest, headers, method, uri},
11    response::Response,
12};
13
14use crate::SignalValues;
15
16/// The header that identifies an HTTP page rerun.
17///
18/// Set `X-Topcoat-Runtime: true` on a `POST` request for
19/// [`RuntimeLayer`](crate::RuntimeLayer) to handle it as a page rerun.
20pub static RUNTIME_HEADER: HeaderName = HeaderName::from_static("x-topcoat-runtime");
21
22/// The value required in the runtime header.
23static RERUN: HeaderValue = HeaderValue::from_static("true");
24
25/// The JSON request body containing the browser's signal values.
26#[derive(Debug, Deserialize)]
27struct PageRerunRequest {
28    #[serde(default)]
29    signals: SignalValues,
30}
31
32/// Checks for a `POST` with the runtime header set to `true`.
33pub(super) fn requested(cx: &Cx) -> bool {
34    *method(cx) == Method::POST && headers(cx).get(&RUNTIME_HEADER) == Some(&RERUN)
35}
36
37/// Reads the signal values and rewrites the request as a `GET` at the same
38/// path and query, with those values in its context.
39///
40/// Returns the rewrite as an error for the router to handle.
41pub(super) async fn dispatch(cx: &Cx, body: Body) -> Result<Response> {
42    let Json(request) = Json::<PageRerunRequest>::from_request(cx, body).await?;
43
44    // The rewritten GET has no body and is no longer a rerun request.
45    let mut headers = headers(cx).clone();
46    headers.remove(&RUNTIME_HEADER);
47    headers.remove(header::CONTENT_TYPE);
48    headers.remove(header::CONTENT_LENGTH);
49
50    let uri = uri(cx);
51    let target = uri
52        .path_and_query()
53        .map_or(uri.path(), |path_and_query| path_and_query.as_str());
54    Err(rewrite(target, Body::empty())
55        .method(Method::GET)
56        .headers(headers)
57        .with(request.signals)
58        .into())
59}