breed_web/
app.rs

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}