use crate::csrf::csrf_token;
use crate::http::{HttpResponse, Request};
use crate::Response;
use ferro_inertia::{InertiaConfig, InertiaRequest as InertiaRequestTrait};
use serde::Serialize;
use std::collections::HashMap;
pub use ferro_inertia::InertiaShared;
impl InertiaRequestTrait for Request {
fn inertia_header(&self, name: &str) -> Option<&str> {
self.header(name)
}
fn path(&self) -> &str {
Request::path(self)
}
}
#[derive(Clone, Debug)]
pub struct SavedInertiaContext {
path: String,
headers: HashMap<String, String>,
}
impl SavedInertiaContext {
pub fn new(req: &Request) -> Self {
let mut headers = HashMap::new();
for name in &[
"X-Inertia",
"X-Inertia-Version",
"X-Inertia-Partial-Data",
"X-Inertia-Partial-Component",
] {
if let Some(value) = req.header(name) {
headers.insert(name.to_string(), value.to_string());
}
}
Self {
path: req.path().to_string(),
headers,
}
}
}
impl From<&Request> for SavedInertiaContext {
fn from(req: &Request) -> Self {
Self::new(req)
}
}
impl InertiaRequestTrait for SavedInertiaContext {
fn inertia_header(&self, name: &str) -> Option<&str> {
self.headers.get(name).map(|s| s.as_str())
}
fn path(&self) -> &str {
&self.path
}
}
pub struct Inertia;
impl Inertia {
pub fn render<P: Serialize>(req: &Request, component: &str, props: P) -> Response {
Self::render_with_config(req, component, props, InertiaConfig::default())
}
pub fn render_with_config<P: Serialize>(
req: &Request,
component: &str,
props: P,
config: InertiaConfig,
) -> Response {
let shared = req.get::<InertiaShared>();
let csrf = csrf_token().unwrap_or_default();
let effective_shared = if let Some(existing) = shared {
let mut shared_clone = existing.clone();
if shared_clone.csrf.is_none() {
shared_clone.csrf = Some(csrf.clone());
}
Some(shared_clone)
} else {
Some(InertiaShared::new().csrf(csrf.clone()))
};
let http_response = ferro_inertia::Inertia::render_with_options(
req,
component,
props,
effective_shared.as_ref(),
config,
);
Ok(Self::convert_response(http_response))
}
pub fn render_ctx<P: Serialize>(
ctx: &SavedInertiaContext,
component: &str,
props: P,
) -> Response {
let csrf = csrf_token().unwrap_or_default();
let shared = InertiaShared::new().csrf(csrf);
let http_response = ferro_inertia::Inertia::render_with_options(
ctx,
component,
props,
Some(&shared),
InertiaConfig::default(),
);
Ok(Self::convert_response(http_response))
}
fn convert_response(inertia_response: ferro_inertia::InertiaHttpResponse) -> HttpResponse {
let mut response = HttpResponse::new()
.header("Content-Type", inertia_response.content_type)
.set_body(inertia_response.body)
.status(inertia_response.status);
for (name, value) in inertia_response.headers {
response = response.header(name, value);
}
response
}
pub fn is_inertia_request(req: &Request) -> bool {
req.is_inertia()
}
pub fn current_url(req: &Request) -> String {
req.path().to_string()
}
pub fn check_version(
req: &Request,
current_version: &str,
redirect_url: &str,
) -> Option<Response> {
ferro_inertia::Inertia::check_version(req, current_version, redirect_url)
.map(|http_response| Ok(Self::convert_response(http_response)))
}
pub fn redirect(req: &Request, path: impl Into<String>) -> Response {
let url = path.into();
let is_inertia = req.is_inertia();
let is_post_like = matches!(req.method().as_str(), "POST" | "PUT" | "PATCH" | "DELETE");
if is_inertia {
let status = if is_post_like { 303 } else { 302 };
Ok(HttpResponse::new()
.status(status)
.header("X-Inertia", "true")
.header("Location", url))
} else {
Ok(HttpResponse::new().status(302).header("Location", url))
}
}
pub fn redirect_ctx(ctx: &SavedInertiaContext, path: impl Into<String>) -> Response {
let url = path.into();
let is_inertia = ctx.headers.contains_key("X-Inertia");
if is_inertia {
Ok(HttpResponse::new()
.status(303)
.header("X-Inertia", "true")
.header("Location", url))
} else {
Ok(HttpResponse::new().status(302).header("Location", url))
}
}
}
#[deprecated(
since = "0.2.0",
note = "Use Inertia::render() instead - thread-local storage is async-unsafe"
)]
pub struct InertiaContext;
#[allow(deprecated)]
impl InertiaContext {
#[deprecated(note = "Use Inertia::render() instead")]
pub fn set(_ctx: InertiaContextData) {
}
#[deprecated(note = "Use Inertia::is_inertia_request(&req) instead")]
pub fn is_inertia_request() -> bool {
false
}
#[deprecated(note = "Use req.path() instead")]
pub fn current_path() -> String {
String::new()
}
#[deprecated(note = "No longer needed")]
pub fn clear() {
}
#[deprecated(note = "Use req methods instead")]
pub fn get() -> Option<InertiaContextData> {
None
}
}
#[deprecated(since = "0.2.0", note = "Use Request methods instead")]
#[derive(Clone, Default)]
pub struct InertiaContextData {
pub path: String,
pub is_inertia: bool,
pub version: Option<String>,
}