pub mod config;
mod instance;
use ayun_core::{traits::ErrorTrait, Error, Result};
use std::{collections::HashMap, ops::Deref};
use tera::Tera;
pub struct View {
inner: Tera,
config: config::View,
}
impl View {
pub fn try_from_config(config: config::View) -> Result<Self, Error> {
let mut tera = Tera::new(&config.path).map_err(Error::wrap)?;
tera.register_function("asset", asset(config.asset.url.to_owned()));
tera.register_function("vite", vite(config.asset.url.to_owned()));
Ok(Self {
inner: tera,
config,
})
}
pub fn config(self) -> config::View {
self.config
}
}
fn asset(url: String) -> impl tera::Function {
Box::new(
move |args: &HashMap<String, serde_json::Value>| -> tera::Result<serde_json::Value> {
let resource = match args.get("resource") {
Some(val) => match serde_json::from_value::<String>(val.clone()) {
Ok(v) => v,
Err(_) => {
let msg = format!(
"Function `asset` received resource={} but `resource` can only be a \
string",
val
);
tracing::error!(err.msg = msg, "error");
return Err(tera::Error::msg(msg));
}
},
None => {
let msg = "Function `asset` didn't receive a `resource` argument";
tracing::error!(err.msg = msg, "error");
return Err(tera::Error::msg(msg));
}
};
Ok(serde_json::Value::String(format!("{}/{}", url, resource)))
},
)
}
fn vite(url: String) -> impl tera::Function {
Box::new(
move |args: &HashMap<String, serde_json::Value>| -> tera::Result<serde_json::Value> {
let entry = match args.get("entry") {
Some(val) => match serde_json::from_value::<String>(val.clone()) {
Ok(v) => v,
Err(_) => {
let msg = format!(
"Function `vite` received entry={} but `entry` can only be a string",
val
);
tracing::error!(err.msg = msg, "error");
return Err(tera::Error::msg(msg));
}
},
None => {
let msg = "Function `vite` didn't receive a `entry` argument";
tracing::error!(err.msg = msg, "error");
return Err(tera::Error::msg(msg));
}
};
if std::path::Path::new("public/hot").exists() {
let dev = format!(
r#"<script type="module" src="http://localhost:5173/@vite/client"></script>
<script type="module" src="http://localhost:5173/{}"></script>"#,
&entry
);
return Ok(serde_json::Value::String(dev));
}
let manifest = match std::fs::read_to_string("public/build/manifest.json").ok() {
None => {
let msg = format!(
"Vite manifest not found at `{}`",
"public/build/manifest.json"
);
tracing::error!(err.msg = msg, "error");
return Err(tera::Error::msg(msg));
}
Some(content) => {
match serde_json::from_str::<serde_json::Value>(&content)?.get(&entry) {
None => {
let msg = format!("Vite manifest entry not found at `{}`", &entry);
tracing::error!(err.msg = msg, "error");
return Err(tera::Error::msg(msg));
}
Some(val) => {
if let Some(is_entry) = val.get("isEntry") {
if !is_entry.as_bool().ok_or_else(|| {
tera::Error::msg("Failed to parse `isEntry` as bool")
})? {
let msg =
format!("Vite manifest entry `{}` is not an entry", &entry);
tracing::error!(err.msg = msg, "error");
return Err(tera::Error::msg(msg));
}
}
val.clone()
}
}
}
};
let mut resources = String::new();
if let Some(css) = manifest.get("css") {
for css in css
.as_array()
.ok_or_else(|| tera::Error::msg("Failed to parse `css` as array"))?
{
resources.push_str(&format!(
r#"<link rel="stylesheet" href="{}/build/{}">"#,
url,
css.as_str()
.ok_or_else(|| tera::Error::msg("Failed to parse `css` as string"))?
));
}
}
if let Some(js) = manifest.get("file") {
resources.push_str(&format!(
r#"<script type="module" src="{}/build/{}"></script>"#,
url,
js.as_str()
.ok_or_else(|| tera::Error::msg("Failed to parse `file` as string"))?
));
}
Ok(serde_json::Value::String(resources))
},
)
}
impl Deref for View {
type Target = Tera;
fn deref(&self) -> &Self::Target {
&self.inner
}
}