ayun_view/support/
view.rs1use crate::{config, error::Error, View, ViewResult};
2use ayun_core::traits::ErrorTrait;
3use std::{collections::HashMap, ops::Deref};
4use tera::Tera;
5
6impl View {
7 pub fn try_from_config(config: config::View) -> ViewResult<Self> {
8 let mut tera = Tera::new(&config.path).map_err(Error::wrap)?;
9
10 tera.register_function("asset", asset(config.asset.url.to_owned()));
11 tera.register_function("vite", vite(config.asset.url.to_owned()));
12
13 Ok(Self {
14 inner: tera,
15 config,
16 })
17 }
18
19 pub fn config(self) -> config::View {
20 self.config
21 }
22}
23
24fn asset(url: String) -> impl tera::Function {
25 Box::new(
26 move |args: &HashMap<String, serde_json::Value>| -> tera::Result<serde_json::Value> {
27 let resource = match args.get("resource") {
28 Some(val) => match serde_json::from_value::<String>(val.clone()) {
29 Ok(v) => v,
30 Err(_) => {
31 let msg = format!(
32 "Function `asset` received resource={} but `resource` can only be a \
33 string",
34 val
35 );
36
37 tracing::error!(err.msg = msg, "error");
38 return Err(tera::Error::msg(msg));
39 }
40 },
41 None => {
42 let msg = "Function `asset` didn't receive a `resource` argument";
43
44 tracing::error!(err.msg = msg, "error");
45 return Err(tera::Error::msg(msg));
46 }
47 };
48
49 Ok(serde_json::Value::String(format!("{}/{}", url, resource)))
50 },
51 )
52}
53
54fn vite(url: String) -> impl tera::Function {
55 Box::new(
56 move |args: &HashMap<String, serde_json::Value>| -> tera::Result<serde_json::Value> {
57 let entry = match args.get("entry") {
58 Some(val) => match serde_json::from_value::<String>(val.clone()) {
59 Ok(v) => v,
60 Err(_) => {
61 let msg = format!(
62 "Function `vite` received entry={} but `entry` can only be a string",
63 val
64 );
65
66 tracing::error!(err.msg = msg, "error");
67 return Err(tera::Error::msg(msg));
68 }
69 },
70 None => {
71 let msg = "Function `vite` didn't receive a `entry` argument";
72
73 tracing::error!(err.msg = msg, "error");
74 return Err(tera::Error::msg(msg));
75 }
76 };
77
78 if std::path::Path::new("public/hot").exists() {
80 let dev = format!(
81 r#"<script type="module" src="http://localhost:5173/@vite/client"></script>
82 <script type="module" src="http://localhost:5173/{}"></script>"#,
83 &entry
84 );
85
86 return Ok(serde_json::Value::String(dev));
87 }
88
89 let manifest = match std::fs::read_to_string("public/build/manifest.json").ok() {
91 None => {
92 let msg = format!(
93 "Vite manifest not found at `{}`",
94 "public/build/manifest.json"
95 );
96
97 tracing::error!(err.msg = msg, "error");
98 return Err(tera::Error::msg(msg));
99 }
100 Some(content) => {
101 match serde_json::from_str::<serde_json::Value>(&content)?.get(&entry) {
102 None => {
103 let msg = format!("Vite manifest entry not found at `{}`", &entry);
104
105 tracing::error!(err.msg = msg, "error");
106 return Err(tera::Error::msg(msg));
107 }
108 Some(val) => {
109 if let Some(is_entry) = val.get("isEntry") {
110 if !is_entry.as_bool().ok_or_else(|| {
111 tera::Error::msg("Failed to parse `isEntry` as bool")
112 })? {
113 let msg =
114 format!("Vite manifest entry `{}` is not an entry", &entry);
115
116 tracing::error!(err.msg = msg, "error");
117 return Err(tera::Error::msg(msg));
118 }
119 }
120
121 val.clone()
122 }
123 }
124 }
125 };
126
127 let mut resources = String::new();
128 if let Some(css) = manifest.get("css") {
129 for css in css
130 .as_array()
131 .ok_or_else(|| tera::Error::msg("Failed to parse `css` as array"))?
132 {
133 resources.push_str(&format!(
134 r#"<link rel="stylesheet" href="{}/build/{}">"#,
135 url,
136 css.as_str()
137 .ok_or_else(|| tera::Error::msg("Failed to parse `css` as string"))?
138 ));
139 }
140 }
141
142 if let Some(js) = manifest.get("file") {
143 resources.push_str(&format!(
144 r#"<script type="module" src="{}/build/{}"></script>"#,
145 url,
146 js.as_str()
147 .ok_or_else(|| tera::Error::msg("Failed to parse `file` as string"))?
148 ));
149 }
150
151 Ok(serde_json::Value::String(resources))
152 },
153 )
154}
155
156impl Deref for View {
157 type Target = Tera;
158
159 fn deref(&self) -> &Self::Target {
160 &self.inner
161 }
162}