use crate::config::InertiaConfig;
use crate::node_process::NodeJsProc;
use crate::props::InertiaProps;
use crate::req_type::InertiaRequestType;
use crate::template_resolver::TemplateResolver;
use crate::{InertiaError, InertiaPage, InertiaSSRPage};
use async_trait::async_trait;
use reqwest::Url;
use serde::{Deserialize, Serialize};
use serde_json::{Map, Value};
use std::collections::HashMap;
use std::io;
pub const X_INERTIA: &str = "x-inertia";
pub const X_INERTIA_LOCATION: &str = "x-inertia-location";
pub const X_INERTIA_VERSION: &str = "x-inertia-version";
pub const X_INERTIA_PARTIAL_COMPONENT: &str = "x-inertia-partial-component";
pub const X_INERTIA_PARTIAL_DATA: &str = "x-inertia-partial-data";
pub const X_INERTIA_PARTIAL_EXCEPT: &str = "x-inertia-partial-except";
pub const X_INERTIA_RESET: &str = "x-inertia-reset";
pub const X_INERTIA_ERROR_BAG: &str = "x-inertia-error-bag";
#[derive(Serialize, Deserialize, PartialEq, Eq, Debug, Clone)]
pub struct Component(pub String);
impl<T> From<T> for Component
where
T: ToString,
{
fn from(value: T) -> Self {
Component(value.to_string())
}
}
pub trait InertiaService {
fn inertia_route(self, path: &str, component: &'static str) -> Self;
}
#[async_trait(?Send)] pub trait InertiaResponder<TResponder, THttpRequest, TRedirect> {
async fn inner_render<'b>(
&'b self,
req: &'b THttpRequest,
component: Component,
props: Option<InertiaProps<'b>>,
) -> Result<TResponder, InertiaError>;
fn inner_back(&self, req: &THttpRequest) -> TRedirect;
fn inner_back_with_errors<Key: ToString>(
&self,
req: &THttpRequest,
errors: HashMap<Key, Value>,
) -> TRedirect;
fn inner_location(req: &THttpRequest, url: &str) -> TResponder;
fn inner_encrypt_history(req: &THttpRequest, encrypt: bool);
fn inner_clear_history(req: &THttpRequest);
}
pub(crate) trait InertiaHttpRequest {
fn should_clear_history(&self) -> bool;
fn should_encrypt_history(&self, default: bool) -> bool;
fn get_merge_props_to_be_reset(&self) -> Vec<&str>;
fn is_inertia_request(&self) -> bool;
fn get_request_type(&self) -> Result<InertiaRequestType, InertiaError>;
fn check_inertia_version(&self, current_version: &str) -> bool;
}
pub enum InertiaVersion<T>
where
T: ToString,
{
Literal(T),
Resolver(Box<dyn FnOnce() -> T>),
}
impl<T> InertiaVersion<T>
where
T: ToString,
{
pub fn resolve(self) -> &'static str {
match self {
InertiaVersion::Literal(v) => v.to_string().leak(),
InertiaVersion::Resolver(resolver) => resolver().to_string().leak(),
}
}
}
pub struct ViewData<'a> {
pub page: InertiaPage<'a>,
pub ssr_page: Option<InertiaSSRPage>,
pub custom_props: Map<String, Value>,
}
#[derive(PartialEq, Debug)]
pub struct SsrClient {
pub(crate) host: &'static str,
pub(crate) port: u16,
}
impl SsrClient {
pub fn new(host: &'static str, port: u16) -> Self {
Self { host, port }
}
}
impl Default for SsrClient {
fn default() -> Self {
Self {
host: "127.0.0.1",
port: 13714,
}
}
}
pub struct Inertia {
#[allow(unused)]
pub(crate) url: &'static str,
pub(crate) version: &'static str,
pub(crate) template_resolver: Box<dyn TemplateResolver + Send + Sync>,
pub(crate) ssr_url: Option<Url>,
pub(crate) encrypt_history: bool,
}
impl Inertia {
pub fn new<V>(config: InertiaConfig<V>) -> Result<Self, io::Error>
where
V: ToString,
{
let version = config.version.resolve();
let ssr_url = match config.with_ssr {
false => None,
true => {
let client: SsrClient = config.custom_ssr_client.unwrap_or_default();
let ssr_url = if client.host.contains("://") {
format!("{}:{}", client.host, client.port)
} else {
format!("http://{}:{}", client.host, client.port)
};
match Url::parse(&ssr_url) {
Err(err) => {
let inertia_err = InertiaError::SsrError(format!(
"Failed to parse Inertia Server url: {}",
err
));
return Err(inertia_err.to_io_error());
}
Ok(url) => Some(url),
}
}
};
Ok(Self {
url: config.url,
version,
template_resolver: config.template_resolver,
ssr_url,
encrypt_history: config.encrypt_history,
})
}
pub fn start_node_server(&self, server_file_path: String) -> Result<NodeJsProc, io::Error> {
if self.ssr_url.is_none() {
let inertia_err: InertiaError = InertiaError::SsrError(
"Ssr is not enabled and, hence, a ssr server cannot be raised.".into(),
);
return Err(inertia_err.to_io_error());
}
let node = NodeJsProc::start(server_file_path, self.ssr_url.as_ref().unwrap());
match node {
Err(err) => Err(InertiaError::NodeJsError(err).to_io_error()),
Ok(process) => Ok(process),
}
}
pub fn get_url(&self) -> &'static str {
self.url
}
pub fn get_version(&self) -> &'static str {
self.version
}
pub fn get_ssr_url(&self) -> &Option<Url> {
&self.ssr_url
}
}