1use std::io::Read;
2use std::path::{PathBuf, Path};
3use std::collections::HashMap;
4use std::fs::File;
5
6use log::info;
7use log::error as log_error;
8use tera::Tera;
9use warp::{Filter, Reply};
10use warp::http::status::StatusCode;
11use warp::reply::Response;
12
13use error::Error;
14use data::GameData;
15use data::monster::Monster;
16use data::breed::{Parent, Formula};
17use crate::skill_detail::SkillDetail;
18use crate::config::Configuration;
19
20trait ToResponse
21{
22 fn toResponse(self) -> Response;
23}
24
25impl ToResponse for Result<String, Error>
26{
27 fn toResponse(self) -> Response
28 {
29 match self
30 {
31 Ok(s) => warp::reply::html(s).into_response(),
32 Err(e) => {
33 log_error!("{}", e);
34 warp::reply::with_status(
35 e.to_string(), StatusCode::INTERNAL_SERVER_ERROR)
36 .into_response()
37 },
38 }
39 }
40}
41
42impl ToResponse for Result<Response, Error>
43{
44 fn toResponse(self) -> Response
45 {
46 match self
47 {
48 Ok(s) => s.into_response(),
49 Err(e) => {
50 log_error!("{}", e);
51 warp::reply::with_status(
52 e.to_string(), StatusCode::INTERNAL_SERVER_ERROR)
53 .into_response()
54 }
55 }
56 }
57}
58
59fn handleIndex(data: &data::GameData, templates: &Tera) -> Result<String, Error>
60{
61 let mut context = tera::Context::new();
62 context.insert("families", &data.monster_data.families);
63 let mut skills: Vec<&str> =
64 data.skills.keys().map(|k| k.as_ref()).collect();
65 skills.sort_unstable();
66 context.insert("skills", &skills);
67
68 templates.render("index.html", &context).map_err(
69 |e| rterr!("Failed to render template index.html: {}", e))
70}
71
72fn handleFamily(family_name: String, data: &data::GameData, templates: &Tera,)
73 -> Result<String, Error>
74{
75 let family_name = urlencoding::decode(&family_name).map_err(
76 |_| rterr!("Invalid family: {}", family_name))?.to_string();
77 if let Some(family) = data.family(&family_name)
78 {
79 let mut context = tera::Context::new();
80 context.insert("family", &family);
81
82 let monsters: Vec<Monster> = data.monstersInFamily(&family)
83 .map(|f| f.clone()).collect();
84 context.insert("monsters", &monsters);
85
86 let parent = Parent::Family(family_name);
87 let forms: Vec<&Formula> = data.usedInFormulae(&parent).collect();
88 context.insert("uses", &forms);
89
90 templates.render("family.html", &context).map_err(
91 |e| rterr!("Failed to render template family.html: {}", e))
92 }
93 else
94 {
95 Err(rterr!("Family not found: {}", family_name))
96 }
97}
98
99fn handleMonster(monster_name: String, data: &data::GameData, templates: &Tera,)
100 -> Result<String, Error>
101{
102 let monster_name = urlencoding::decode(&monster_name).map_err(
103 |_| rterr!("Invalid monster: {}", monster_name))?.to_string();
104 if let Some(monster) = data.monster(&monster_name)
105 {
106 let mut context = tera::Context::new();
107 context.insert("monster", &monster);
108
109 let family = data.family(&monster.family).ok_or_else(
110 || rterr!("Invalid family '{}' of monster {}", monster.family,
111 monster.name))?;
112 context.insert("family_name", &family.name);
113
114 let breeds: Vec<&Formula> =
115 data.breedFromFormulae(&monster_name).collect();
116 context.insert("breeds", &breeds);
117
118 let parent = Parent::Monster(monster_name);
119 let uses: Vec<&Formula> = data.usedInFormulae(&parent).collect();
120 context.insert("uses", &uses);
121
122 templates.render("monster.html", &context).map_err(
123 |e| rterr!("Failed to render template monster.html: {}", e))
124 }
125 else
126 {
127 Err(rterr!("Monster not found: {}", monster_name))
128 }
129
130}
131
132fn handleSkill(skill_name: String, data: &data::GameData, templates: &Tera,)
133 -> Result<String, Error>
134{
135 let skill_name = urlencoding::decode(&skill_name).map_err(
136 |_| rterr!("Invalid monster: {}", skill_name))?.to_string();
137 if let Some(skill) = data.skill(&skill_name)
138 {
139 let mut context = tera::Context::new();
140 context.insert("skill", &SkillDetail::fromSkill(skill, data).unwrap());
141 templates.render("skill.html", &context).map_err(
142 |e| rterr!("Failed to render template skill.html: {}", e))
143 }
144 else
145 {
146 Err(rterr!("Skill not found: {}", skill_name))
147 }
148}
149
150fn urlEncode(s: &str) -> String
151{
152 urlencoding::encode(s).to_string()
153}
154
155fn urlFor(name: &str, arg: &str) -> String
156{
157 match name
158 {
159 "index" => String::from("/"),
160 "family" => String::from("/family/") + &urlEncode(arg),
161 "monster" => String::from("/monster/") + &urlEncode(arg),
162 "skill" => String::from("/skill/") + &urlEncode(arg),
163 "static" => String::from("/static/") + &urlEncode(arg),
164 _ => String::from("/"),
165 }
166}
167
168fn getTeraFuncArgs(args: &HashMap<String, tera::Value>, arg_name: &str) ->
169 tera::Result<String>
170{
171 let value = args.get(arg_name);
172 if value.is_none()
173 {
174 return Err(format!("Argument {} not found in function call.", arg_name)
175 .into());
176 }
177 let value: String = tera::from_value(value.unwrap().clone())?;
178 Ok(value)
179}
180
181fn makeURLFor(serve_path: String) -> impl tera::Function
182{
183 move |args: &HashMap<String, tera::Value>| ->
184 tera::Result<tera::Value> {
185 let path_prefix: String = if serve_path == "" || serve_path == "/"
186 {
187 String::new()
188 }
189 else if serve_path.starts_with("/")
190 {
191 serve_path.to_owned()
192 }
193 else
194 {
195 String::from("/") + &serve_path
196 };
197
198 let name = getTeraFuncArgs(args, "name")?;
199 let arg = getTeraFuncArgs(args, "arg")?;
200 Ok(tera::to_value(path_prefix + &urlFor(&name, &arg)).unwrap())
201 }
202}
203
204pub struct App
205{
206 data: data::GameData,
207 templates: Tera,
208 config: Configuration,
209}
210
211impl App
212{
213 pub fn new(config: Configuration) -> Result<Self, Error>
214 {
215 let mut result = Self {
216 data: data::GameData::default(),
217 templates: Tera::default(),
218 config,
219 };
220 result.init()?;
221 Ok(result)
222 }
223
224 fn init(&mut self) -> Result<(), Error>
225 {
226 {
227 let data_path = Path::new(&self.config.data_dir)
228 .join("monster-data.xml");
229 let mut data_file = File::open(data_path).map_err(
230 |_| rterr!("Failed to open data file"))?;
231 let mut raw_data: Vec<u8> = Vec::new();
232 data_file.read_to_end(&mut raw_data).map_err(
233 |_| rterr!("Failed to read data file"))?;
234 self.data = GameData::fromXML(&raw_data)?;
235 }
236
237 let template_path = PathBuf::from(&self.config.data_dir)
238 .join("templates").canonicalize()
239 .map_err(|_| rterr!("Invalid template dir"))?
240 .join("**").join("*");
241 info!("Template dir is {}", template_path.display());
242 let template_dir = template_path.to_str().ok_or_else(
243 || rterr!("Invalid template path"))?;
244 self.templates = Tera::new(template_dir).map_err(
245 |e| rterr!("Failed to compile templates: {}", e))?;
246 self.templates.register_function(
247 "url_for", makeURLFor(self.config.serve_under_path.clone()));
248 Ok(())
249 }
250
251 pub async fn serve(self) -> Result<(), Error>
252 {
253 let static_dir = PathBuf::from(&self.config.data_dir).join("static");
254 info!("Static dir is {}", static_dir.display());
255 let statics = warp::path("static").and(warp::fs::dir(static_dir));
256
257 let data = self.data.clone();
258 let temp = self.templates.clone();
259 let index = warp::path::end().map(move || {
260 handleIndex(&data, &temp).toResponse()
261 });
262
263 let data = self.data.clone();
264 let temp = self.templates.clone();
265 let family = warp::path("family").and(warp::path::param()).map(
266 move |param: String| {
267 handleFamily(param, &data, &temp).toResponse()
268 });
269
270 let data = self.data.clone();
271 let temp = self.templates.clone();
272 let monster = warp::path("monster").and(warp::path::param()).map(
273 move |param: String| {
274 handleMonster(param, &data, &temp).toResponse()
275 });
276
277 let data = self.data.clone();
278 let temp = self.templates.clone();
279 let skill = warp::path("skill").and(warp::path::param()).map(
280 move |param: String| {
281 handleSkill(param, &data, &temp).toResponse()
282 });
283
284 let route = if self.config.serve_under_path == String::from("/") ||
285 self.config.serve_under_path.is_empty()
286 {
287 statics.or(index).or(family).or(monster).or(skill).boxed()
288 }
289 else
290 {
291 let mut segs = self.config.serve_under_path.split('/');
292 if self.config.serve_under_path.starts_with("/")
293 {
294 segs.next();
295 }
296 let first: String = segs.next().unwrap().to_owned();
297 let mut r = warp::path(first).boxed();
298 for seg in segs
299 {
300 r = r.and(warp::path(seg.to_owned())).boxed();
301 }
302 r.and(statics.or(index).or(family).or(monster).or(skill)).boxed()
303 };
304
305 info!("Listening at {}:{}...", self.config.listen_address,
306 self.config.listen_port);
307
308 warp::serve(warp::get().and(route)).run(
309 std::net::SocketAddr::new(
310 self.config.listen_address.parse().map_err(
311 |_| rterr!("Invalid listen address: {}",
312 self.config.listen_address))?,
313 self.config.listen_port)).await;
314 Ok(())
315 }
316}