From a33473fdc9e31f571b4cf672b046f669363ea621 Mon Sep 17 00:00:00 2001 From: Slatian Date: Sun, 29 Oct 2023 15:23:47 +0100 Subject: [PATCH] Moved to a more genral implementation for the response format. --- src/main.rs | 4 +- src/mycelium/format.rs | 128 +++++++++++++++++++++++++++++++++++++++ src/mycelium/mod.rs | 4 ++ src/settings.rs | 31 +--------- src/templating_engine.rs | 13 ++-- 5 files changed, 143 insertions(+), 37 deletions(-) create mode 100644 src/mycelium/format.rs diff --git a/src/main.rs b/src/main.rs index d4b10f7..9678841 100644 --- a/src/main.rs +++ b/src/main.rs @@ -379,7 +379,7 @@ async fn settings_query_middleware( let ua = user_agent.as_str(); for tua in config.template.text_user_agents { if ua.starts_with(&tua) { - format = Some(ResponseFormat::TextPlain); + format = Some(ResponseFormat::Text); break; } } @@ -387,7 +387,7 @@ async fn settings_query_middleware( } // Add the request settings extension req.extensions_mut().insert(QuerySettings{ - format: format.unwrap_or(ResponseFormat::TextHtml), + format: format.unwrap_or(ResponseFormat::Html), lang: query.lang.unwrap_or("en".to_string()), available_dns_resolvers: derived_config.dns_resolver_selectables, dns_resolver_id: dns_resolver_id, diff --git a/src/mycelium/format.rs b/src/mycelium/format.rs new file mode 100644 index 0000000..c1d6285 --- /dev/null +++ b/src/mycelium/format.rs @@ -0,0 +1,128 @@ + +use serde::Deserialize; +use serde::Serialize; + +/// Defines how the response should be rendered. +pub enum MycFormatFamily { + /// When rendering the templating engine will be invoked + Template, + /// When rendering the [View](./trait.View.html) + /// is asked to generate an API response. + API, +} + +/// Implement on a type that is able to describe a response format. +/// +/// It is best implemented on an enum. +pub trait MycFormat: ToString+Clone+Default { + + // Return the format family this + fn get_family(&self) -> MycFormatFamily { + match self.get_name().as_str() { + "json" => MycFormatFamily::API, + _ => MycFormatFamily::Template, + } + } + + /// Returns the file extnsion for the format. + /// + /// Used for deriving the path for the template name. + /// + /// Defaults to `.{self.get_name()}` + /// with the exception of the name being `text` + /// then it defaults to `.txt`. + fn get_file_extension(&self) -> String { + match self.get_name().as_str() { + "text" => ".txt".to_string(), + _ => ".".to_owned()+&self.get_name(), + } + } + + /// Returns the name of the format, + /// by default taken from the ToString implementation. + fn get_name(&self) -> String { + self.to_string() + } + + /// Allows adding extra mimetypes quickly for prototyping + /// + /// Implementing get_mime_type() properly is recommended + /// for production use. + fn get_less_well_known_mimetype(&self) -> Option<&'static str> { + None + } + + /// Returns a textual representation of the Mimetype. + /// + /// It is recommended to implement this when in production use. + /// + /// For prototyping the default implementation makes assumptions + /// based on the output of get_name(), falling back + /// to get_less_well_known_mimetype() and the "application/octet-stream" type. + /// + /// The default implementation knows the following associations: + /// * `text`: `text/plain` + /// * `html`: `text/html` + /// * `json`: `application/json` + /// * `xml`: `application/xml` + /// * `rss`: `application/rss+xml` + /// * `atom`: `application/atom+xml` + /// + /// *Implementation Note:* It may be possible that two different views + /// have the same MimeType (maybe two json representations for different consumers). + /// + fn get_mime_type(&self) -> &'static str { + match self.get_name().as_str() { + "text" => "text/plain", + "html" => "text/html", + "json" => "application/json", + "xml" => "application/xml", + "rss" => "application/rss+xml", + "atom" => "application/atom+xml", + _ => + self.get_less_well_known_mimetype() + .unwrap_or_else(||"application/octet-stream"), + } + } + + /// Constructs a view from its name. + fn from_name(name: &str) -> Option; +} + +// Some Sample implementations + +#[derive(Clone,Serialize,Deserialize,Default)] +#[serde(rename_all="lowercase")] +pub enum HtmlTextJsonFormat { + #[default] + Html, + Text, + Json, +} + +impl ToString for HtmlTextJsonFormat { + fn to_string(&self) -> String { + match self { + Self::Html => "html", + Self::Text => "text", + Self::Json => "json", + }.to_owned() + } +} + + +impl MycFormat for HtmlTextJsonFormat { + + //TODO: implement other methods to make it more performant + + fn from_name(name: &str) -> Option { + match name { + "html" => Some(Self::Html), + "text" => Some(Self::Text), + "json" => Some(Self::Json), + _ => None, + } + } +} + + diff --git a/src/mycelium/mod.rs b/src/mycelium/mod.rs index 1241614..a2b6d9d 100644 --- a/src/mycelium/mod.rs +++ b/src/mycelium/mod.rs @@ -1,3 +1,7 @@ +mod format; mod view; +pub use self::format::HtmlTextJsonFormat; +pub use self::format::MycFormat; +pub use self::format::MycFormatFamily; pub use self::view::MycView; diff --git a/src/settings.rs b/src/settings.rs index b5ebbfb..7fb7223 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,38 +1,11 @@ use serde::{Deserialize,Serialize}; use std::sync::Arc; +use crate::mycelium::HtmlTextJsonFormat; /* Response format */ -#[derive(Deserialize, 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() - } -} - -impl ResponseFormat { - pub fn to_file_extension(&self) -> String { - match self { - ResponseFormat::TextPlain => ".txt", - ResponseFormat::TextHtml => ".html", - ResponseFormat::ApplicationJson => ".json", - }.to_string() - } -} +pub type ResponseFormat = HtmlTextJsonFormat; /* Query and Template Settings */ diff --git a/src/templating_engine.rs b/src/templating_engine.rs index 6bc1197..d2a7e53 100644 --- a/src/templating_engine.rs +++ b/src/templating_engine.rs @@ -19,6 +19,8 @@ use toml::Table; use crate::view::View; use crate::mycelium::MycView; +use crate::mycelium::MycFormat; +use crate::mycelium::MycFormatFamily; use crate::settings::QuerySettings; use crate::settings::ResponseFormat; @@ -36,8 +38,8 @@ impl Engine { settings: &QuerySettings, view: &View, ) -> Response { - let mut response = match settings.format { - ResponseFormat::TextHtml | ResponseFormat::TextPlain => { + let mut response = match settings.format.get_family() { + MycFormatFamily::Template => { let template_name = view.get_template_name(); let mut context = tera::Context::new(); @@ -50,10 +52,10 @@ impl Engine { context.insert("data", &view); context.insert("extra", &self.template_config); - match self.tera.render(&(template_name.clone()+&settings.format.to_file_extension()), &context) { + match self.tera.render(&(template_name.clone()+&settings.format.get_file_extension()), &context) { Ok(text) => match settings.format { - ResponseFormat::TextHtml => Html(text).into_response(), + ResponseFormat::Html => Html(text).into_response(), _ => text.into_response(), } Err(e) => { @@ -65,8 +67,7 @@ impl Engine { } } } - //TODO: Plain Text should have its own matcher - ResponseFormat::ApplicationJson => { + MycFormatFamily::API => { match view { View::Dig{result, ..} => { Json(result).into_response()