Moved templating to its own module

This commit is contained in:
Slatian 2023-02-12 17:53:50 +01:00
parent febcb8b02e
commit 0193c24385
2 changed files with 122 additions and 100 deletions

View File

@ -1,11 +1,7 @@
use axum::{ use axum::{
extract::Query, extract::Query,
extract::State, extract::State,
http::StatusCode,
response::Html,
response::IntoResponse,
response::Response, response::Response,
response::Json,
Router, Router,
routing::get, routing::get,
}; };
@ -16,82 +12,37 @@ use trust_dns_resolver::{
config::ResolverConfig, config::ResolverConfig,
}; };
use std::net::{IpAddr, Ipv4Addr}; use std::net::{IpAddr, Ipv4Addr};
use std::sync::Arc; use std::sync::Arc;
mod simple_dns; mod simple_dns;
mod templating_engine;
#[derive(serde::Deserialize, serde::Serialize, Clone, Copy)] use crate::templating_engine::View;
enum ResponseFormat { use crate::templating_engine::ResponseFormat;
#[serde(rename="text/plain", alias="text")]
TextPlain,
#[serde(rename="text/html", alias="html")]
TextHtml,
#[serde(rename="application/json", alias="json")]
ApplicationJson,
}
impl ToString for ResponseFormat {
fn to_string(&self) -> String {
match self {
ResponseFormat::TextPlain => "text/plain",
ResponseFormat::TextHtml => "text/html",
ResponseFormat::ApplicationJson => "application/json",
}.to_string()
}
}
#[derive(serde::Deserialize, serde::Serialize)] #[derive(serde::Deserialize, serde::Serialize)]
struct IpQuery { pub struct IpQuery {
ip: Option<IpAddr>, ip: Option<IpAddr>,
format: Option<ResponseFormat>, format: Option<ResponseFormat>,
} }
#[derive(serde::Deserialize, serde::Serialize)] #[derive(serde::Deserialize, serde::Serialize)]
struct DigQuery { pub struct DigQuery {
name: String, name: String,
format: Option<ResponseFormat>, format: Option<ResponseFormat>,
} }
#[derive(serde::Deserialize, serde::Serialize)] #[derive(serde::Deserialize, serde::Serialize)]
struct IpResult { pub struct IpResult {
hostname: Option<String>, hostname: Option<String>,
} }
struct TemplatingEngine {
tera: Tera,
}
struct ServiceSharedState { struct ServiceSharedState {
templating_engine: TemplatingEngine, templating_engine: templating_engine::Engine,
dns_resolver: TokioAsyncResolver, dns_resolver: TokioAsyncResolver,
} }
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(untagged)]
enum EchoipView {
Dig { query: DigQuery, result: simple_dns::DnsLookupResult },
Index { query: IpQuery, result: IpResult },
Ip { query: IpQuery, result: IpResult },
Message(String),
#[serde(rename="404")]
NotFound,
}
impl EchoipView {
fn template_name(&self) -> String {
match self {
EchoipView::Dig{..} => "dig",
EchoipView::Index{..} => "index",
EchoipView::Ip{..} => "ip",
EchoipView::Message(..) => "message",
EchoipView::NotFound => "404",
}.to_string()
}
}
#[tokio::main] #[tokio::main]
async fn main() { async fn main() {
// Initalize Tera templates // Initalize Tera templates
@ -119,7 +70,7 @@ async fn main() {
// Initialize shared state // Initialize shared state
let shared_state = Arc::new(ServiceSharedState{ let shared_state = Arc::new(ServiceSharedState{
templating_engine: TemplatingEngine{ templating_engine: templating_engine::Engine{
tera: tera, tera: tera,
}, },
dns_resolver: dns_resolver, dns_resolver: dns_resolver,
@ -148,50 +99,10 @@ async fn hello_world_handler(
state.templating_engine.render_view( state.templating_engine.render_view(
ResponseFormat::TextPlain, ResponseFormat::TextPlain,
EchoipView::Message("Hello! There, You, Awesome Creature!".to_string()) View::Message("Hello! There, You, Awesome Creature!".to_string())
).await ).await
} }
impl TemplatingEngine {
async fn render_view(
&self,
format: ResponseFormat,
view: EchoipView,
) -> Response {
match format {
ResponseFormat::TextHtml => {
let template_name = view.template_name();
let mut context = tera::Context::new();
context.insert("view", &template_name);
//intented for shared macros
context.insert("format", &format.to_string());
context.insert("data", &view);
match self.tera.render(&(template_name+".html"), &context) {
Ok(html) => Html(html).into_response(),
Err(e) => {
println!("There was an error while rendering index.html: {e:?}");
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
}
//TODO: Plain Text should have its own matcher
ResponseFormat::ApplicationJson | ResponseFormat::TextPlain => {
match view {
EchoipView::Dig{result, ..} => {
Json(result).into_response()
},
EchoipView::Index{result, ..} | EchoipView::Ip{result, ..} => {
Json(result).into_response()
},
_ => Json(view).into_response(),
}
}
}
}
}
async fn handle_default_route( async fn handle_default_route(
Query(ip_query): Query<IpQuery>, Query(ip_query): Query<IpQuery>,
State(arc_state): State<Arc<ServiceSharedState>>, State(arc_state): State<Arc<ServiceSharedState>>,
@ -211,7 +122,7 @@ async fn handle_default_route(
state.templating_engine.render_view( state.templating_engine.render_view(
format, format,
EchoipView::Index{query: ip_query, result: result} View::Index{query: ip_query, result: result}
).await ).await
} }
@ -228,7 +139,7 @@ async fn handle_dig_route(
state.templating_engine.render_view( state.templating_engine.render_view(
format, format,
EchoipView::Dig{ query: dig_query, result: dig_result} View::Dig{ query: dig_query, result: dig_result}
).await ).await
} }

111
src/templating_engine.rs Normal file
View File

@ -0,0 +1,111 @@
/*
* This is the echoip-slatecave templating engine.
* It wraps around tera in is specialized for echoip-slatecave.
*/
use axum::{
http::StatusCode,
response::Html,
response::IntoResponse,
response::Response,
response::Json,
};
use tera::Tera;
use crate::simple_dns;
use crate::IpResult;
use crate::IpQuery;
use crate::DigQuery;
/* Response format */
#[derive(serde::Deserialize, serde::Serialize, Clone, Copy)]
pub enum ResponseFormat {
#[serde(rename="text/plain", alias="text")]
TextPlain,
#[serde(rename="text/html", alias="html")]
TextHtml,
#[serde(rename="application/json", alias="json")]
ApplicationJson,
}
impl ToString for ResponseFormat {
fn to_string(&self) -> String {
match self {
ResponseFormat::TextPlain => "text/plain",
ResponseFormat::TextHtml => "text/html",
ResponseFormat::ApplicationJson => "application/json",
}.to_string()
}
}
/* The echoip view */
#[derive(serde::Deserialize, serde::Serialize)]
#[serde(untagged)]
pub enum View {
Dig { query: DigQuery, result: simple_dns::DnsLookupResult },
Index { query: IpQuery, result: IpResult },
Ip { query: IpQuery, result: IpResult },
Message(String),
#[serde(rename="404")]
NotFound,
}
impl View {
pub fn template_name(&self) -> String {
match self {
View::Dig{..} => "dig",
View::Index{..} => "index",
View::Ip{..} => "ip",
View::Message(..) => "message",
View::NotFound => "404",
}.to_string()
}
}
/* The engine itself */
pub struct Engine {
pub tera: Tera,
}
impl Engine {
pub async fn render_view(
&self,
format: ResponseFormat,
view: View,
) -> Response {
match format {
ResponseFormat::TextHtml => {
let template_name = view.template_name();
let mut context = tera::Context::new();
context.insert("view", &template_name);
//intented for shared macros
context.insert("format", &format.to_string());
context.insert("data", &view);
match self.tera.render(&(template_name+".html"), &context) {
Ok(html) => Html(html).into_response(),
Err(e) => {
println!("There was an error while rendering index.html: {e:?}");
StatusCode::INTERNAL_SERVER_ERROR.into_response()
}
}
}
//TODO: Plain Text should have its own matcher
ResponseFormat::ApplicationJson | ResponseFormat::TextPlain => {
match view {
View::Dig{result, ..} => {
Json(result).into_response()
},
View::Index{result, ..} | View::Ip{result, ..} => {
Json(result).into_response()
},
_ => Json(view).into_response(),
}
}
}
}
}