Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Determine Content-Type during compilation #594

Merged
merged 5 commits into from
Jan 7, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions askama/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,14 @@ with-rocket = ["askama_derive/rocket"]
with-tide = ["askama_derive/tide"]
with-warp = ["askama_derive/warp"]

# deprecated
mime = []
mime_guess = []

[dependencies]
askama_derive = { version = "0.11.0", path = "../askama_derive" }
askama_escape = { version = "0.10", path = "../askama_escape" }
askama_shared = { version = "0.12.0", path = "../askama_shared", default-features = false }
mime = { version = "0.3", optional = true }
djc marked this conversation as resolved.
Show resolved Hide resolved
mime_guess = { version = "2", optional = true }

[package.metadata.docs.rs]
features = ["config", "humansize", "num-traits", "serde-json", "serde-yaml"]
40 changes: 15 additions & 25 deletions askama/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,9 @@ pub trait Template {

/// Provides a conservative estimate of the expanded length of the rendered template
const SIZE_HINT: usize;

/// The MIME type (Content-Type) of the data that gets rendered by this Template
const MIME_TYPE: &'static str;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we deprecate EXTENSION and extension()? I feel like this is probably better.

}

/// Object-safe wrapper trait around [`Template`] implementers
Expand All @@ -103,6 +106,9 @@ pub trait DynTemplate {

/// Provides a conservative estimate of the expanded length of the rendered template
fn size_hint(&self) -> usize;

/// The MIME type (Content-Type) of the data that gets rendered by this Template
fn mime_type(&self) -> &'static str;
}

impl<T: Template> DynTemplate for T {
Expand All @@ -121,40 +127,22 @@ impl<T: Template> DynTemplate for T {
fn size_hint(&self) -> usize {
Self::SIZE_HINT
}

fn mime_type(&self) -> &'static str {
Self::MIME_TYPE
}
}

pub use crate::shared::filters;
pub use crate::shared::helpers;
pub use crate::shared::{read_config_file, Error, MarkupDisplay, Result};
pub use askama_derive::*;

#[deprecated(since = "0.11.1", note = "The only function in this mod is deprecated")]
pub mod mime {
djc marked this conversation as resolved.
Show resolved Hide resolved
#[cfg(all(feature = "mime_guess", feature = "mime"))]
pub fn extension_to_mime_type(ext: &str) -> mime_guess::Mime {
let basic_type = mime_guess::from_ext(ext).first_or_octet_stream();
for (simple, utf_8) in &TEXT_TYPES {
if &basic_type == simple {
return utf_8.clone();
}
}
basic_type
}

#[cfg(all(feature = "mime_guess", feature = "mime"))]
const TEXT_TYPES: [(mime_guess::Mime, mime_guess::Mime); 6] = [
(mime::TEXT_PLAIN, mime::TEXT_PLAIN_UTF_8),
(mime::TEXT_HTML, mime::TEXT_HTML_UTF_8),
(mime::TEXT_CSS, mime::TEXT_CSS_UTF_8),
(mime::TEXT_CSV, mime::TEXT_CSV_UTF_8),
(
mime::TEXT_TAB_SEPARATED_VALUES,
mime::TEXT_TAB_SEPARATED_VALUES_UTF_8,
),
(
mime::APPLICATION_JAVASCRIPT,
mime::APPLICATION_JAVASCRIPT_UTF_8,
),
];
#[deprecated(since = "0.11.1", note = "Use Template::MIME_TYPE instead")]
pub use crate::shared::extension_to_mime_type;
}

/// Old build script helper to rebuild crates if contained templates have changed
Expand Down Expand Up @@ -184,6 +172,8 @@ mod tests {
const EXTENSION: Option<&'static str> = Some("txt");

const SIZE_HINT: usize = 4;

const MIME_TYPE: &'static str = "text/plain; charset=utf-8";
}

fn render(t: &dyn DynTemplate) -> String {
Expand Down
11 changes: 4 additions & 7 deletions askama_actix/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
use std::fmt;

use actix_web::body::BoxBody;
use actix_web::http::header::HeaderValue;
use actix_web::http::StatusCode;
use actix_web::{HttpResponse, HttpResponseBuilder, ResponseError};
use askama::mime::extension_to_mime_type;
pub use askama::*;

/// Newtype to let askama::Error implement actix_web::ResponseError.
Expand Down Expand Up @@ -36,12 +36,9 @@ pub trait TemplateToResponse {
impl<T: askama::Template> TemplateToResponse for T {
fn to_response(&self) -> HttpResponse<BoxBody> {
match self.render() {
Ok(buffer) => {
let ctype = extension_to_mime_type(T::EXTENSION.unwrap_or("txt"));
HttpResponseBuilder::new(StatusCode::OK)
.content_type(ctype)
.body(buffer)
}
Ok(buffer) => HttpResponseBuilder::new(StatusCode::OK)
.content_type(HeaderValue::from_static(T::MIME_TYPE))
.body(buffer),
Err(err) => HttpResponse::from_error(ActixError(err)),
}
}
Expand Down
6 changes: 3 additions & 3 deletions askama_axum/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ pub use http::Response;
use http::StatusCode;
use http_body::{Empty, Full};

pub fn into_response<T: Template>(t: &T, ext: &str) -> Response<BoxBody> {
pub fn into_response<T: Template>(t: &T, _ext: &str) -> Response<BoxBody> {
match t.render() {
Ok(body) => Response::builder()
.status(StatusCode::OK)
.header(
"content-type",
askama::mime::extension_to_mime_type(ext).to_string(),
http::header::CONTENT_TYPE,
http::HeaderValue::from_static(T::MIME_TYPE),
)
.body(body::boxed(Full::from(body)))
.unwrap(),
Expand Down
6 changes: 3 additions & 3 deletions askama_gotham/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ pub use gotham::handler::IntoResponse;
pub use gotham::state::State;
pub use hyper::{Body, Response, StatusCode};

pub fn respond<T: Template>(t: &T, ext: &str) -> Response<Body> {
pub fn respond<T: Template>(t: &T, _ext: &str) -> Response<Body> {
match t.render() {
Ok(body) => Response::builder()
.status(StatusCode::OK)
.header(
"content-type",
mime::extension_to_mime_type(ext).to_string(),
hyper::header::CONTENT_TYPE,
hyper::header::HeaderValue::from_static(T::MIME_TYPE),
)
.body(body.into())
.unwrap(),
Expand Down
19 changes: 6 additions & 13 deletions askama_mendes/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,18 @@
#![deny(elided_lifetimes_in_paths)]
#![deny(unreachable_pub)]

use std::convert::TryFrom;

use mendes::application::{Application, Responder};
use mendes::http::header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE};
use mendes::http::request::Parts;
use mendes::http::Response;
use mime_guess::MimeGuess;

pub use askama::*;

pub fn into_response<A, T>(
app: &A,
req: &Parts,
t: &T,
ext: Option<&str>,
_ext: Option<&str>,
) -> Response<A::ResponseBody>
where
A: Application,
Expand All @@ -29,13 +26,9 @@ where
Err(e) => return <A::Error as From<_>>::from(e).into_response(app, req),
};

let mut builder = Response::builder();
builder = builder.header(CONTENT_LENGTH, content.len());
if let Some(ext) = ext {
if let Some(ty) = MimeGuess::from_ext(ext).first() {
builder = builder.header(CONTENT_TYPE, HeaderValue::try_from(ty.as_ref()).unwrap());
}
}

builder.body(content.into()).unwrap()
Response::builder()
.header(CONTENT_LENGTH, content.len())
.header(CONTENT_TYPE, HeaderValue::from_static(T::MIME_TYPE))
.body(content.into())
.unwrap()
}
2 changes: 1 addition & 1 deletion askama_mendes/tests/basic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ async fn test() {
rsp.headers
.get("content-type")
.and_then(|hv| hv.to_str().ok()),
Some("text/plain")
Some("text/plain; charset=utf-8")
);
assert_eq!(to_bytes(body).await.unwrap(), &b"Hello, world!"[..]);
}
Expand Down
7 changes: 3 additions & 4 deletions askama_rocket/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,15 @@
use std::io::Cursor;

pub use askama::*;
use rocket::http::{ContentType, Status};
use rocket::http::{Header, Status};
pub use rocket::request::Request;
use rocket::response::Response;
pub use rocket::response::{Responder, Result};

pub fn respond<T: Template>(t: &T, ext: &str) -> Result<'static> {
pub fn respond<T: Template>(t: &T, _ext: &str) -> Result<'static> {
let rsp = t.render().map_err(|_| Status::InternalServerError)?;
let ctype = ContentType::from_extension(ext).ok_or(Status::InternalServerError)?;
Response::build()
.header(ctype)
.header(Header::new("content-type", T::MIME_TYPE))
.sized_body(Cursor::new(rsp))
.ok()
}
2 changes: 2 additions & 0 deletions askama_shared/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ yaml = ["serde", "serde_yaml"]
[dependencies]
askama_escape = { version = "0.10.2", path = "../askama_escape" }
humansize = { version = "1.1.0", optional = true }
mime = "0.3"
mime_guess = "2"
nom = "7"
num-traits = { version = "0.2.6", optional = true }
proc-macro2 = "1"
Expand Down
4 changes: 4 additions & 0 deletions askama_shared/src/generator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,10 @@ impl<'a, S: std::hash::BuildHasher> Generator<'a, S> {
buf.writeln(&format!("{}", size_hint))?;
buf.writeln(";")?;

buf.writeln("const MIME_TYPE: &'static ::std::primitive::str = ")?;
buf.writeln(&format!("{:?}", &self.input.mime_type))?;
buf.writeln(";")?;

buf.writeln("}")?;
Ok(())
}
Expand Down
47 changes: 43 additions & 4 deletions askama_shared/src/input.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use crate::{CompileError, Config, Syntax};
use std::path::{Path, PathBuf};
use std::str::FromStr;

use mime::Mime;
use quote::ToTokens;

pub struct TemplateInput<'a> {
Expand All @@ -13,6 +14,7 @@ pub struct TemplateInput<'a> {
pub print: Print,
pub escaper: &'a str,
pub ext: Option<String>,
pub mime_type: String,
Kijewski marked this conversation as resolved.
Show resolved Hide resolved
pub parent: Option<&'a syn::Type>,
pub path: PathBuf,
}
Expand Down Expand Up @@ -160,7 +162,7 @@ impl TemplateInput<'_> {

// Match extension against defined output formats

let extension = escaping.unwrap_or_else(|| {
let escaping = escaping.unwrap_or_else(|| {
path.extension()
.map(|s| s.to_str().unwrap())
.unwrap_or("")
Expand All @@ -169,16 +171,20 @@ impl TemplateInput<'_> {

let mut escaper = None;
for (extensions, path) in &config.escapers {
if extensions.contains(&extension) {
if extensions.contains(&escaping) {
escaper = Some(path);
break;
}
}

let escaper = escaper.ok_or_else(|| {
CompileError::String(format!("no escaper defined for extension '{}'", extension,))
CompileError::String(format!("no escaper defined for extension '{}'", escaping,))
})?;

let mime_type =
extension_to_mime_type(ext_default_to_path(ext.as_deref(), &path).unwrap_or("txt"))
.to_string();

Ok(TemplateInput {
ast,
config,
Expand All @@ -187,16 +193,23 @@ impl TemplateInput<'_> {
print,
escaper,
ext,
mime_type,
parent,
path,
})
}

#[inline]
pub fn extension(&self) -> Option<&str> {
self.ext.as_deref().or_else(|| extension(&self.path))
ext_default_to_path(self.ext.as_deref(), &self.path)
}
}

#[inline]
pub fn ext_default_to_path<'a>(ext: Option<&'a str>, path: &'a Path) -> Option<&'a str> {
ext.or_else(|| extension(path))
}

fn extension(path: &Path) -> Option<&str> {
let ext = path.extension().map(|s| s.to_str().unwrap())?;

Expand Down Expand Up @@ -239,6 +252,32 @@ impl FromStr for Print {
}
}

#[doc(hidden)]
pub fn extension_to_mime_type(ext: &str) -> Mime {
let basic_type = mime_guess::from_ext(ext).first_or_octet_stream();
for (simple, utf_8) in &TEXT_TYPES {
if &basic_type == simple {
return utf_8.clone();
}
}
basic_type
}

const TEXT_TYPES: [(Mime, Mime); 6] = [
(mime::TEXT_PLAIN, mime::TEXT_PLAIN_UTF_8),
(mime::TEXT_HTML, mime::TEXT_HTML_UTF_8),
(mime::TEXT_CSS, mime::TEXT_CSS_UTF_8),
(mime::TEXT_CSV, mime::TEXT_CSV_UTF_8),
(
mime::TEXT_TAB_SEPARATED_VALUES,
mime::TEXT_TAB_SEPARATED_VALUES_UTF_8,
),
(
mime::APPLICATION_JAVASCRIPT,
mime::APPLICATION_JAVASCRIPT_UTF_8,
),
];

#[cfg(test)]
mod tests {
use super::*;
Expand Down
1 change: 1 addition & 0 deletions askama_shared/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ use std::{env, fmt, fs};
#[cfg(feature = "serde")]
use serde::Deserialize;

pub use crate::input::extension_to_mime_type;
pub use askama_escape::MarkupDisplay;

mod error;
Expand Down
10 changes: 3 additions & 7 deletions askama_tide/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,12 @@ pub use askama;
pub use tide;

use askama::*;
use tide::{http::Mime, Body, Response};
use tide::{Body, Response};

pub fn try_into_body<T: Template>(t: &T, ext: &str) -> Result<Body> {
pub fn try_into_body<T: Template>(t: &T, _ext: &str) -> Result<Body> {
let string = t.render()?;
let mut body = Body::from_string(string);

if let Some(mime) = Mime::from_extension(ext) {
body.set_mime(mime);
}

body.set_mime(T::MIME_TYPE);
Ok(body)
}

Expand Down
7 changes: 2 additions & 5 deletions askama_warp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,11 @@ use warp::http::{self, header, StatusCode};
use warp::hyper::Body;
use warp::reply::Response;

pub fn reply<T: askama::Template>(t: &T, ext: &str) -> Response {
pub fn reply<T: askama::Template>(t: &T, _ext: &str) -> Response {
match t.render() {
Ok(body) => http::Response::builder()
.status(StatusCode::OK)
.header(
header::CONTENT_TYPE,
mime::extension_to_mime_type(ext).to_string(),
)
.header(header::CONTENT_TYPE, T::MIME_TYPE)
.body(body.into()),
Err(_) => http::Response::builder()
.status(StatusCode::INTERNAL_SERVER_ERROR)
Expand Down