Skip to main content

rama_http/service/web/endpoint/extract/
datastar.rs

1//! [🚀 Datastar](https://data-star.dev/) support extractor for rama.
2
3use crate::service::web::{
4    extract::{FromRequest, OptionalFromRequest},
5    response::IntoResponse,
6};
7use rama_core::telemetry::tracing;
8use rama_http_types::{BodyExtractExt, Method, Request, Response, StatusCode};
9use serde::{Deserialize, de::DeserializeOwned};
10
11/// [`ReadSignals`] is a request extractor that reads datastar signals from the request.
12#[derive(Debug)]
13pub struct ReadSignals<T>(pub T);
14
15#[derive(Deserialize)]
16struct DatastarParam {
17    datastar: serde_json::Value,
18}
19
20impl<T> FromRequest for ReadSignals<T>
21where
22    T: DeserializeOwned + Send + Sync + 'static,
23{
24    type Rejection = Response;
25
26    async fn from_request(req: Request) -> Result<Self, Self::Rejection> {
27        let json = match *req.method() {
28            Method::GET => {
29                let param = req.uri().query_params::<DatastarParam>().map_err(|err| {
30                    tracing::debug!(
31                        "failed to parse datastar query params from GET request: {err:?}"
32                    );
33                    (StatusCode::BAD_REQUEST, err.to_string()).into_response()
34                })?;
35
36                let signals = param.datastar.as_str().ok_or_else(|| {
37                    tracing::debug!("failed to get datastar query value from GET request");
38                    (StatusCode::BAD_REQUEST, "Failed to parse JSON").into_response()
39                })?;
40
41                serde_json::from_str(signals).map_err(|err| {
42                    tracing::debug!(
43                        "failed to parse datastar query json value from GET request: {err:?}"
44                    );
45                    (StatusCode::BAD_REQUEST, err.to_string()).into_response()
46                })?
47            }
48            _ => req.into_body().try_into_json().await.map_err(|err| {
49                tracing::debug!("failed to parse datastar json payload from POST request: {err:?}");
50                (StatusCode::BAD_REQUEST, err.to_string()).into_response()
51            })?,
52        };
53
54        Ok(Self(json))
55    }
56}
57
58impl<T> OptionalFromRequest for ReadSignals<T>
59where
60    T: DeserializeOwned + Send + Sync + 'static,
61{
62    type Rejection = Response;
63
64    async fn from_request(req: Request) -> Result<Option<Self>, Self::Rejection> {
65        if req.headers().get("datastar-request").is_none() {
66            tracing::trace!(
67                "no datastar request header present: returning no read signals as such"
68            );
69            return Ok(None);
70        }
71        Ok(Some(<Self as FromRequest>::from_request(req).await?))
72    }
73}