Moved to a more genral implementation for the response format.

This commit is contained in:
Slatian 2023-10-29 15:23:47 +01:00
parent 20fb7ee2ff
commit a33473fdc9
5 changed files with 143 additions and 37 deletions

View File

@ -379,7 +379,7 @@ async fn settings_query_middleware<B>(
let ua = user_agent.as_str(); let ua = user_agent.as_str();
for tua in config.template.text_user_agents { for tua in config.template.text_user_agents {
if ua.starts_with(&tua) { if ua.starts_with(&tua) {
format = Some(ResponseFormat::TextPlain); format = Some(ResponseFormat::Text);
break; break;
} }
} }
@ -387,7 +387,7 @@ async fn settings_query_middleware<B>(
} }
// Add the request settings extension // Add the request settings extension
req.extensions_mut().insert(QuerySettings{ 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()), lang: query.lang.unwrap_or("en".to_string()),
available_dns_resolvers: derived_config.dns_resolver_selectables, available_dns_resolvers: derived_config.dns_resolver_selectables,
dns_resolver_id: dns_resolver_id, dns_resolver_id: dns_resolver_id,

128
src/mycelium/format.rs Normal file
View File

@ -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<Self>;
}
// 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<Self> {
match name {
"html" => Some(Self::Html),
"text" => Some(Self::Text),
"json" => Some(Self::Json),
_ => None,
}
}
}

View File

@ -1,3 +1,7 @@
mod format;
mod view; mod view;
pub use self::format::HtmlTextJsonFormat;
pub use self::format::MycFormat;
pub use self::format::MycFormatFamily;
pub use self::view::MycView; pub use self::view::MycView;

View File

@ -1,38 +1,11 @@
use serde::{Deserialize,Serialize}; use serde::{Deserialize,Serialize};
use std::sync::Arc; use std::sync::Arc;
use crate::mycelium::HtmlTextJsonFormat;
/* Response format */ /* Response format */
#[derive(Deserialize, Serialize, Clone, Copy)] pub type ResponseFormat = HtmlTextJsonFormat;
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()
}
}
/* Query and Template Settings */ /* Query and Template Settings */

View File

@ -19,6 +19,8 @@ use toml::Table;
use crate::view::View; use crate::view::View;
use crate::mycelium::MycView; use crate::mycelium::MycView;
use crate::mycelium::MycFormat;
use crate::mycelium::MycFormatFamily;
use crate::settings::QuerySettings; use crate::settings::QuerySettings;
use crate::settings::ResponseFormat; use crate::settings::ResponseFormat;
@ -36,8 +38,8 @@ impl Engine {
settings: &QuerySettings, settings: &QuerySettings,
view: &View, view: &View,
) -> Response { ) -> Response {
let mut response = match settings.format { let mut response = match settings.format.get_family() {
ResponseFormat::TextHtml | ResponseFormat::TextPlain => { MycFormatFamily::Template => {
let template_name = view.get_template_name(); let template_name = view.get_template_name();
let mut context = tera::Context::new(); let mut context = tera::Context::new();
@ -50,10 +52,10 @@ impl Engine {
context.insert("data", &view); context.insert("data", &view);
context.insert("extra", &self.template_config); 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) => Ok(text) =>
match settings.format { match settings.format {
ResponseFormat::TextHtml => Html(text).into_response(), ResponseFormat::Html => Html(text).into_response(),
_ => text.into_response(), _ => text.into_response(),
} }
Err(e) => { Err(e) => {
@ -65,8 +67,7 @@ impl Engine {
} }
} }
} }
//TODO: Plain Text should have its own matcher MycFormatFamily::API => {
ResponseFormat::ApplicationJson => {
match view { match view {
View::Dig{result, ..} => { View::Dig{result, ..} => {
Json(result).into_response() Json(result).into_response()