karbon_framework/inertia/
inertia_response.rs1use axum::http::{header, HeaderMap, StatusCode};
2use axum::response::{Html, IntoResponse, Response};
3use serde::Serialize;
4
5pub const INERTIA_HEADER: &str = "x-inertia";
7
8pub const INERTIA_VERSION_HEADER: &str = "x-inertia-version";
10
11#[derive(Debug, Clone, Serialize)]
13pub struct InertiaPage {
14 pub component: String,
15 pub props: serde_json::Value,
16 pub url: String,
17 pub version: String,
18}
19
20#[derive(Clone)]
22pub struct InertiaConfig {
23 pub root_template: String,
26 pub version: String,
28}
29
30impl InertiaConfig {
31 pub fn new(root_template: &str) -> Self {
32 Self {
33 root_template: root_template.to_string(),
34 version: String::new(),
35 }
36 }
37
38 pub fn version(mut self, version: &str) -> Self {
39 self.version = version.to_string();
40 self
41 }
42}
43
44impl Default for InertiaConfig {
45 fn default() -> Self {
46 Self::new(DEFAULT_TEMPLATE)
47 }
48}
49
50const DEFAULT_TEMPLATE: &str = r#"<!DOCTYPE html>
52<html lang="en">
53<head>
54 <meta charset="UTF-8">
55 <meta name="viewport" content="width=device-width, initial-scale=1.0">
56 {{INERTIA_HEAD}}
57</head>
58<body>
59 <div id="app" data-page='{{INERTIA_PAGE}}'></div>
60 {{INERTIA_SCRIPTS}}
61</body>
62</html>"#;
63
64pub struct Inertia;
78
79impl Inertia {
80 pub fn render<T: Serialize>(component: &str, props: T) -> InertiaResponse {
85 let props = serde_json::to_value(props).unwrap_or_default();
86 InertiaResponse {
87 component: component.to_string(),
88 props,
89 }
90 }
91
92 pub fn location(url: &str) -> Response {
94 (StatusCode::SEE_OTHER, [(header::LOCATION, url)]).into_response()
95 }
96}
97
98pub struct InertiaResponse {
101 pub component: String,
102 pub props: serde_json::Value,
103}
104
105impl InertiaResponse {
106 pub fn into_response_with(
110 self,
111 request_headers: &HeaderMap,
112 url: &str,
113 config: &InertiaConfig,
114 ) -> Response {
115 let page = InertiaPage {
116 component: self.component,
117 props: self.props,
118 url: url.to_string(),
119 version: config.version.clone(),
120 };
121
122 let is_inertia = request_headers.contains_key(INERTIA_HEADER);
123
124 if is_inertia {
125 let mut response = axum::Json(&page).into_response();
127 response
128 .headers_mut()
129 .insert(INERTIA_HEADER, "true".parse().unwrap());
130 response.headers_mut().insert(
131 "vary",
132 "X-Inertia".parse().unwrap(),
133 );
134 response
135 } else {
136 let page_json = serde_json::to_string(&page).unwrap_or_default();
138 let page_escaped = page_json
140 .replace('&', "&")
141 .replace('"', """)
142 .replace('\'', "'")
143 .replace('<', "<")
144 .replace('>', ">");
145
146 let html = config
147 .root_template
148 .replace("{{INERTIA_PAGE}}", &page_escaped)
149 .replace("{{INERTIA_HEAD}}", "")
150 .replace("{{INERTIA_SCRIPTS}}", "");
151
152 Html(html).into_response()
153 }
154 }
155}