From 747f9eb56fc4efbdc9ac28b3fb838e61e1fcb349 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Thu, 30 Jul 2020 11:19:44 +0200 Subject: [PATCH 1/9] Larger tide example. Provide a tide example similar to the other framework examples, with static files and error handling. --- examples/tide/Cargo.toml | 8 +- examples/tide/src/build.rs | 3 + examples/tide/src/main.rs | 102 ++++++++++++++++++++++--- examples/tide/statics/btfl.svg | 1 + examples/tide/statics/cloud.svg | 1 + examples/tide/statics/grass.svg | 1 + examples/tide/statics/squirrel.jpg | Bin 0 -> 12640 bytes examples/tide/style.scss | 74 ++++++++++++++++++ examples/tide/templates/error.rs.html | 25 ++++++ examples/tide/templates/footer.rs.html | 14 ++++ examples/tide/templates/page.rs.html | 36 +++++++++ 11 files changed, 252 insertions(+), 13 deletions(-) create mode 100644 examples/tide/statics/btfl.svg create mode 100644 examples/tide/statics/cloud.svg create mode 100644 examples/tide/statics/grass.svg create mode 100644 examples/tide/statics/squirrel.jpg create mode 100644 examples/tide/style.scss create mode 100644 examples/tide/templates/error.rs.html create mode 100644 examples/tide/templates/footer.rs.html create mode 100644 examples/tide/templates/page.rs.html diff --git a/examples/tide/Cargo.toml b/examples/tide/Cargo.toml index 310356a..2b18ab2 100644 --- a/examples/tide/Cargo.toml +++ b/examples/tide/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "tide" +name = "ructe-tide" version = "0.4.0" authors = ["Rasmus Kaj "] edition = "2018" @@ -7,8 +7,10 @@ edition = "2018" build = "src/build.rs" [build-dependencies] -ructe = { path = "../.." } +ructe = { path = "../..", features = ["mime03", "sass"] } [dependencies] async-std = { version = "1.6.0", features = ["attributes"] } -tide = "0.10.0" +tide = "0.12.0" +mime = "0.3.16" +httpdate = "0.3.1" diff --git a/examples/tide/src/build.rs b/examples/tide/src/build.rs index d8df3d6..530c625 100644 --- a/examples/tide/src/build.rs +++ b/examples/tide/src/build.rs @@ -5,5 +5,8 @@ use ructe::{Ructe, RucteError}; fn main() -> Result<(), RucteError> { let mut ructe = Ructe::from_env()?; + let mut statics = ructe.statics()?; + statics.add_files("statics")?; + statics.add_sass_file("style.scss")?; ructe.compile_templates("templates") } diff --git a/examples/tide/src/main.rs b/examples/tide/src/main.rs index a1357b9..93d2732 100644 --- a/examples/tide/src/main.rs +++ b/examples/tide/src/main.rs @@ -1,20 +1,29 @@ -// And finally, include the generated code for templates and static files. -include!(concat!(env!("OUT_DIR"), "/templates.rs")); - +//! An example of how ructe can be used with the tide framework. mod ructe_tide; use ructe_tide::Render; -use tide::{Response, StatusCode}; +use httpdate::fmt_http_date; +use std::future::Future; +use std::io::{self, Write}; +use std::pin::Pin; +use std::str::FromStr; +use std::time::{Duration, SystemTime}; +use templates::statics::{cloud_svg, StaticFile}; +use tide::http::headers::EXPIRES; +use tide::http::{mime, Error}; +use tide::{Next, Redirect, Request, Response, StatusCode}; +/// Main entry point. +/// +/// Set up an app and start listening for requests. #[async_std::main] async fn main() -> Result<(), std::io::Error> { let mut app = tide::new(); - - app.at("/").get(|_| async { - let mut res = Response::new(StatusCode::Ok); - res.render_html(|o| Ok(templates::hello(o, "world")?))?; - Ok(res) - }); + app.middleware(handle_error); + app.at("/static/*path").get(static_file); + app.at("/favicon.ico") + .get(Redirect::new(format!("/static/{}", cloud_svg.name))); + app.at("/").get(frontpage); let addr = "127.0.0.1:3000"; println!("Starting server on http://{}/", addr); @@ -22,3 +31,76 @@ async fn main() -> Result<(), std::io::Error> { Ok(()) } + +/// Handler for a page in the web site. +async fn frontpage(_req: Request<()>) -> Result { + // A real site would probably have some business logic here. + let mut res = Response::new(StatusCode::Ok); + res.render_html(|o| { + Ok(templates::page(o, &[("world", 5), ("tide", 7)])?) + })?; + Ok(res) +} + +/// Handler for static files. +/// +/// Ructe provides the static files as constants, and the StaticFile +/// interface to get a file by url path. +async fn static_file(req: Request<()>) -> Result { + let path = req.param::("path")?; + let data = StaticFile::get(&path) + .ok_or_else(|| Error::from_str(StatusCode::NotFound, "not found"))?; + Ok(Response::builder(StatusCode::Ok) + // TODO: Provide http_types::Mime directly in ructe. + .content_type(mime::Mime::from_str(data.mime.as_ref()).unwrap()) + .header(EXPIRES, fmt_http_date(SystemTime::now() + 180 * DAY)) + .body(data.content) + .build()) +} + +/// 24 hours. +const DAY: Duration = Duration::from_secs(24 * 60 * 60); + +/// This method can be used as a "template tag", i.e. a method that +/// can be called directly from a template. +fn footer(out: &mut dyn Write) -> io::Result<()> { + templates::footer( + out, + &[ + ("ructe", "https://crates.io/crates/ructe"), + ("tide", "https://crates.io/crates/tide"), + ], + ) +} + +/// A middleware to log errors and render a html error message. +/// +/// If the response has content, this function does not overwrite it. +fn handle_error<'a>( + request: Request<()>, + next: Next<'a, ()>, +) -> Pin> + Send + 'a>> { + Box::pin(async { + // I don't really like to create this string for every request, + // but when I see if there is an error, the request is consumed. + let rdesc = format!("{} {:?}", request.method(), request.url()); + let mut res = next.run(request).await; + let status = res.status(); + if status.is_client_error() || status.is_server_error() { + println!("Error {} on {}: {:?}", status, rdesc, res.error()); + if res.is_empty().unwrap_or(false) { + res.render_html(|o| { + Ok(templates::error( + o, + status, + status.canonical_reason(), + )?) + })? + } + } + Ok(res) + }) +} + +// And finally, include the generated code for templates and static files. +include!(concat!(env!("OUT_DIR"), "/templates.rs")); diff --git a/examples/tide/statics/btfl.svg b/examples/tide/statics/btfl.svg new file mode 100644 index 0000000..dcf4c41 --- /dev/null +++ b/examples/tide/statics/btfl.svg @@ -0,0 +1 @@ + diff --git a/examples/tide/statics/cloud.svg b/examples/tide/statics/cloud.svg new file mode 100644 index 0000000..3984def --- /dev/null +++ b/examples/tide/statics/cloud.svg @@ -0,0 +1 @@ + diff --git a/examples/tide/statics/grass.svg b/examples/tide/statics/grass.svg new file mode 100644 index 0000000..a743517 --- /dev/null +++ b/examples/tide/statics/grass.svg @@ -0,0 +1 @@ + diff --git a/examples/tide/statics/squirrel.jpg b/examples/tide/statics/squirrel.jpg new file mode 100644 index 0000000000000000000000000000000000000000..900f4098054a6dc0295a9c75cb82ea993f807862 GIT binary patch literal 12640 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 literal 0 HcmV?d00001 diff --git a/examples/tide/style.scss b/examples/tide/style.scss new file mode 100644 index 0000000..fbcd696 --- /dev/null +++ b/examples/tide/style.scss @@ -0,0 +1,74 @@ +html, body { + margin: 0; + padding: 0; +} + +body { + background: left bottom / auto 9% repeat-x url(static-name(grass.svg)) fixed, + 82% 96% / 5vh auto no-repeat url(static-name(btfl.svg)) fixed, + center bottom / auto 10% repeat-x url(static-name(grass.svg)) fixed, + right bottom / auto 11% repeat-x url(static-name(grass.svg)) fixed, + 10% 90% / 8vh auto no-repeat url(static-name(btfl.svg)) fixed, + linear-gradient(#519dd2, #7ec0ec) fixed; +} + +main { + padding: 2ex 3ex; + margin: 8vh auto 1em; + max-width: 37em; + background: white; + border-radius: 1em; + position: relative; + + &:before, &:after { + content: url(static-name(cloud.svg)); + display: block; + position: absolute; + z-index: -1; + } + &:before { + width: 52%; + top: -7vh; + left: -7%; + } + &:after { + width: 30%; + top: -6vh; + right: -4%; + } +} + +footer { + background: #84ff5e; + border-radius: 1em 0 0; + bottom: 0; + padding: 0 1em; + position: fixed; + right: 0; +} + +h1 { + margin: 0 0 1ex; +} +figure { + float: right; + margin: 0 0 1ex 1em; +} +p { + margin: 0 0 1em; +} + +.error { + body { + background: left bottom / auto 9% repeat-x url(static-name(grass.svg)) fixed, + 82% 98% / 5vh auto no-repeat url(static-name(btfl.svg)) fixed, + 10% 97% / 6vh auto no-repeat url(static-name(btfl.svg)) fixed, + right bottom / auto 11% repeat-x url(static-name(grass.svg)) fixed, + linear-gradient(#89a, #568) fixed; + } + main { + background: linear-gradient(#fff, #fff, #eee, #ccc, #888) padding-box; + border: solid 1px rgba(white, 0.3); + border-width: 0 2px 2px 1px; + } +} diff --git a/examples/tide/templates/error.rs.html b/examples/tide/templates/error.rs.html new file mode 100644 index 0000000..218d546 --- /dev/null +++ b/examples/tide/templates/error.rs.html @@ -0,0 +1,25 @@ +@use super::statics::*; +@use crate::footer; +@use tide::StatusCode; + +@(code: StatusCode, message: &str) + + + + + Error @(u16::from(code)): @code.canonical_reason() + + + + +
+

@code.canonical_reason()

+ +

@message

+

We are sorry about this. + In a real application, this would mention the incident having + been logged, and giving contact details for further reporting.

+
+ @:footer() + + diff --git a/examples/tide/templates/footer.rs.html b/examples/tide/templates/footer.rs.html new file mode 100644 index 0000000..533dbe8 --- /dev/null +++ b/examples/tide/templates/footer.rs.html @@ -0,0 +1,14 @@ +@(frameworks: &[(&str, &str)]) + +
+ @if let Some(((last_name, last_href), prev)) = frameworks.split_last() { + Made with + @if let Some(((last_name, last_href), prev)) = prev.split_last() { + @for (name, href) in prev { + @name, + } + @last_name and + } + @last_name. + } +
diff --git a/examples/tide/templates/page.rs.html b/examples/tide/templates/page.rs.html new file mode 100644 index 0000000..c8ad365 --- /dev/null +++ b/examples/tide/templates/page.rs.html @@ -0,0 +1,36 @@ +@use super::statics::*; +@use crate::footer; + +@(paras: &[(&str, usize)]) + + + + + Example + + + + +
+

Example

+ +

This is a simple sample page, + to be served with warp. + It contains an image of a squirrel and not much more.

+ +
+ Squirrel! +
A squirrel
+
+ + @for (order, n) in paras { +

This is a @order paragraph, with @n repeats. + @for _ in 1..=*n { + This is a @order paragraph. + } + } +

+ @:footer() + + From a56a292c4e779ac087ea8cb8830ec94e95a6b748 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Fri, 31 Jul 2020 16:19:04 +0200 Subject: [PATCH 2/9] Provide Mime support for http-types. Tide uses a diffrent flavor of Mime from the other frameworks I have tried. Add support for that. --- Cargo.toml | 1 + examples/tide/Cargo.toml | 4 ++-- examples/tide/src/main.rs | 6 ++---- src/lib.rs | 8 ++++++-- src/staticfiles.rs | 31 ++++++++++++++++++++++++++++--- 5 files changed, 39 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 83789d3..29b3445 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ sass = ["rsass"] mime02 = [] mime03 = ["mime"] warp02 = ["mime03"] +http-types = [] [dependencies] base64 = "^0.12.0" diff --git a/examples/tide/Cargo.toml b/examples/tide/Cargo.toml index 2b18ab2..b5ee68c 100644 --- a/examples/tide/Cargo.toml +++ b/examples/tide/Cargo.toml @@ -7,10 +7,10 @@ edition = "2018" build = "src/build.rs" [build-dependencies] -ructe = { path = "../..", features = ["mime03", "sass"] } +ructe = { path = "../..", features = ["http-types", "sass"] } [dependencies] async-std = { version = "1.6.0", features = ["attributes"] } tide = "0.12.0" -mime = "0.3.16" httpdate = "0.3.1" +http-types = "2.3.0" diff --git a/examples/tide/src/main.rs b/examples/tide/src/main.rs index 93d2732..8c81f2f 100644 --- a/examples/tide/src/main.rs +++ b/examples/tide/src/main.rs @@ -6,11 +6,10 @@ use httpdate::fmt_http_date; use std::future::Future; use std::io::{self, Write}; use std::pin::Pin; -use std::str::FromStr; use std::time::{Duration, SystemTime}; use templates::statics::{cloud_svg, StaticFile}; use tide::http::headers::EXPIRES; -use tide::http::{mime, Error}; +use tide::http::Error; use tide::{Next, Redirect, Request, Response, StatusCode}; /// Main entry point. @@ -51,8 +50,7 @@ async fn static_file(req: Request<()>) -> Result { let data = StaticFile::get(&path) .ok_or_else(|| Error::from_str(StatusCode::NotFound, "not found"))?; Ok(Response::builder(StatusCode::Ok) - // TODO: Provide http_types::Mime directly in ructe. - .content_type(mime::Mime::from_str(data.mime.as_ref()).unwrap()) + .content_type(data.mime.clone()) // Takes Into, not AsRef .header(EXPIRES, fmt_http_date(SystemTime::now() + 180 * DAY)) .body(data.content) .build()) diff --git a/src/lib.rs b/src/lib.rs index 7e0da92..6237e87 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -90,13 +90,17 @@ //! version 0.2.x of the [mime] crate. //! * `warp02` -- Provide an extension to [`Response::Builder`] to //! simplify template rendering in the [warp] framework, versions 0.2.x. +//! * `http-types` -- Static files know their mime types, compatible with +//! the [http-types] crate. //! //! [`response::Builder`]: ../http/response/struct.Builder.html //! [mime]: https://crates.rs/crates/mime //! [warp]: https://crates.rs/crates/warp +//! [http-types]: https://crates.rs/crates/http-types //! -//! The `mime02` and `mime03` features are mutually exclusive and -//! requires a dependency on a matching version of `mime`. +//! The `mime02`, `mime03`, and `http-types` features are mutually +//! exclusive and requires a dependency on a matching version of +//! `mime` or `http-types`. //! Any of them can be combined with the `sass` feature. //! //! ```toml diff --git a/src/staticfiles.rs b/src/staticfiles.rs index 683d4e5..71e9ae1 100644 --- a/src/staticfiles.rs +++ b/src/staticfiles.rs @@ -208,6 +208,9 @@ impl StaticFiles { if cfg!(feature = "mime03") { src.write_all(b"extern crate mime;\nuse self::mime::Mime;\n\n")?; } + if cfg!(feature = "http-types") { + src.write_all(b"use http_types::mime::{self, Mime};\n\n")?; + } src.write_all( b"/// A static file has a name (so its url can be recognized) and the /// actual file contents. @@ -226,6 +229,9 @@ pub struct StaticFile { if cfg!(feature = "mime03") { src.write_all(b" pub mime: &'static Mime,\n")?; } + if cfg!(feature = "http-types") { + src.write_all(b" pub mime: &'static Mime,\n")?; + } src.write_all( b"} #[allow(dead_code)] @@ -588,12 +594,13 @@ fn checksum_slug(data: &[u8]) -> String { } #[cfg(not(feature = "mime02"))] #[cfg(not(feature = "mime03"))] +#[cfg(not(feature = "http-types"))] fn mime_arg(_: &str) -> String { "".to_string() } #[cfg(feature = "mime02")] fn mime_arg(suffix: &str) -> String { - format!("_mime: {:?},\n", mime_from_suffix(suffix)) + format!(" _mime: {:?},\n", mime_from_suffix(suffix)) } #[cfg(feature = "mime02")] @@ -614,9 +621,9 @@ fn mime_from_suffix(suffix: &str) -> &'static str { } } -#[cfg(feature = "mime03")] +#[cfg(any(feature = "mime03", feature = "http-types"))] fn mime_arg(suffix: &str) -> String { - format!("mime: &mime::{},\n", mime_from_suffix(suffix)) + format!(" mime: &mime::{},\n", mime_from_suffix(suffix)) } #[cfg(feature = "mime03")] @@ -636,3 +643,21 @@ fn mime_from_suffix(suffix: &str) -> &'static str { _ => "APPLICATION_OCTET_STREAM", } } + +#[cfg(feature = "http-types")] +fn mime_from_suffix(suffix: &str) -> &'static str { + match suffix.to_lowercase().as_ref() { + "css" => "CSS", + "html" | "htm" => "CSS", + "ico" => "ICO", + "jpg" | "jpeg" => "JPEG", + "js" | "jsonp" => "JAVASCRIPT", + "json" => "JSON", + "png" => "PNG", + "svg" => "SVG", + "txt" => "PLAIN", + "wasm" => "WASM", + "xml" => "XML", + _ => "mime::BYTE_STREAM", + } +} From 42af59c3d3256de0662e159b782328194aaf002e Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Fri, 31 Jul 2020 20:31:36 +0200 Subject: [PATCH 3/9] Serve the favicon directly. As a separate static file. Includes some refactoring of the static_file handler. --- examples/tide/src/main.rs | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/examples/tide/src/main.rs b/examples/tide/src/main.rs index 8c81f2f..7e8e5b9 100644 --- a/examples/tide/src/main.rs +++ b/examples/tide/src/main.rs @@ -10,7 +10,7 @@ use std::time::{Duration, SystemTime}; use templates::statics::{cloud_svg, StaticFile}; use tide::http::headers::EXPIRES; use tide::http::Error; -use tide::{Next, Redirect, Request, Response, StatusCode}; +use tide::{Next, Request, Response, StatusCode}; /// Main entry point. /// @@ -20,8 +20,7 @@ async fn main() -> Result<(), std::io::Error> { let mut app = tide::new(); app.middleware(handle_error); app.at("/static/*path").get(static_file); - app.at("/favicon.ico") - .get(Redirect::new(format!("/static/{}", cloud_svg.name))); + app.at("/favicon.ico").get(favicon); app.at("/").get(frontpage); let addr = "127.0.0.1:3000"; @@ -47,13 +46,25 @@ async fn frontpage(_req: Request<()>) -> Result { /// interface to get a file by url path. async fn static_file(req: Request<()>) -> Result { let path = req.param::("path")?; - let data = StaticFile::get(&path) - .ok_or_else(|| Error::from_str(StatusCode::NotFound, "not found"))?; - Ok(Response::builder(StatusCode::Ok) + StaticFile::get(&path) + .ok_or_else(|| Error::from_str(StatusCode::NotFound, "not found")) + .map(static_response) +} + +/// Specialized static file handler for the favicon +async fn favicon(_req: Request<()>) -> Result { + Ok(static_response(&cloud_svg)) +} + +/// Make a response from a StaticFile +/// +/// Helper for static_file and favicon. +fn static_response(data: &StaticFile) -> Response { + Response::builder(StatusCode::Ok) .content_type(data.mime.clone()) // Takes Into, not AsRef .header(EXPIRES, fmt_http_date(SystemTime::now() + 180 * DAY)) .body(data.content) - .build()) + .build() } /// 24 hours. From 8e8fcbb8b021cd6b211eb9f5b8e74045e0205244 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Fri, 31 Jul 2020 21:06:12 +0200 Subject: [PATCH 4/9] Provide a builder-style render interface. --- examples/tide/src/main.rs | 17 ++++++++------- examples/tide/src/ructe_tide.rs | 37 +++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+), 8 deletions(-) diff --git a/examples/tide/src/main.rs b/examples/tide/src/main.rs index 7e8e5b9..4b1812c 100644 --- a/examples/tide/src/main.rs +++ b/examples/tide/src/main.rs @@ -1,6 +1,6 @@ //! An example of how ructe can be used with the tide framework. mod ructe_tide; -use ructe_tide::Render; +use ructe_tide::{Render, RenderBuilder}; use httpdate::fmt_http_date; use std::future::Future; @@ -33,11 +33,9 @@ async fn main() -> Result<(), std::io::Error> { /// Handler for a page in the web site. async fn frontpage(_req: Request<()>) -> Result { // A real site would probably have some business logic here. - let mut res = Response::new(StatusCode::Ok); - res.render_html(|o| { - Ok(templates::page(o, &[("world", 5), ("tide", 7)])?) - })?; - Ok(res) + Ok(Response::builder(StatusCode::Ok) + .render_html(|o| templates::page(o, &[("world", 5), ("tide", 7)])) + .build()) } /// Handler for static files. @@ -98,12 +96,15 @@ fn handle_error<'a>( if status.is_client_error() || status.is_server_error() { println!("Error {} on {}: {:?}", status, rdesc, res.error()); if res.is_empty().unwrap_or(false) { + // Note: We are adding a body to an existing response, + // so the builder patern cannot be used here. + // The Render trait is provided for Response. res.render_html(|o| { - Ok(templates::error( + templates::error( o, status, status.canonical_reason(), - )?) + ) })? } } diff --git a/examples/tide/src/ructe_tide.rs b/examples/tide/src/ructe_tide.rs index edc9e23..c4e2885 100644 --- a/examples/tide/src/ructe_tide.rs +++ b/examples/tide/src/ructe_tide.rs @@ -28,3 +28,40 @@ impl Render for tide::Response { Ok(()) } } + +pub trait RenderBuilder { + fn render(self, call: Call) -> tide::ResponseBuilder + where + Call: FnOnce(&mut dyn std::io::Write) -> std::io::Result<()>; + + fn render_html(self, call: Call) -> tide::ResponseBuilder + where + Call: FnOnce(&mut dyn std::io::Write) -> std::io::Result<()>; +} + +impl RenderBuilder for tide::ResponseBuilder { + fn render(self, call: Call) -> tide::ResponseBuilder + where + Call: FnOnce(&mut dyn std::io::Write) -> std::io::Result<()>, + { + let mut buf = Vec::new(); + match call(&mut buf) { + Ok(()) => self.body(buf), + Err(e) => { + // NOTE: A tide::Response may contain an Error, but there + // seem to be no way of setting that in a ResponseBuilder, + // so I just log the error and return a builder for a + // generic internal server error. + tide::log::error!("Failed to render response: {}", e); + tide::Response::builder(500) + } + } + } + + fn render_html(self, call: Call) -> tide::ResponseBuilder + where + Call: FnOnce(&mut dyn std::io::Write) -> std::io::Result<()>, + { + self.content_type(tide::http::mime::HTML).render(call) + } +} From f4aee1256d7b1b999bca9f53f315dc5a430b18dd Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Fri, 31 Jul 2020 21:19:25 +0200 Subject: [PATCH 5/9] Rustfmt. --- examples/tide/src/main.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/examples/tide/src/main.rs b/examples/tide/src/main.rs index 4b1812c..840e280 100644 --- a/examples/tide/src/main.rs +++ b/examples/tide/src/main.rs @@ -100,11 +100,7 @@ fn handle_error<'a>( // so the builder patern cannot be used here. // The Render trait is provided for Response. res.render_html(|o| { - templates::error( - o, - status, - status.canonical_reason(), - ) + templates::error(o, status, status.canonical_reason()) })? } } From 26a8d9eaa6d76acbe14640bae6db8d0767d0cba1 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Fri, 31 Jul 2020 23:01:07 +0200 Subject: [PATCH 6/9] Update tide to 0.13. --- examples/tide/Cargo.toml | 2 +- examples/tide/src/main.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/tide/Cargo.toml b/examples/tide/Cargo.toml index b5ee68c..13dec38 100644 --- a/examples/tide/Cargo.toml +++ b/examples/tide/Cargo.toml @@ -11,6 +11,6 @@ ructe = { path = "../..", features = ["http-types", "sass"] } [dependencies] async-std = { version = "1.6.0", features = ["attributes"] } -tide = "0.12.0" +tide = "0.13.0" httpdate = "0.3.1" http-types = "2.3.0" diff --git a/examples/tide/src/main.rs b/examples/tide/src/main.rs index 840e280..39cd3c6 100644 --- a/examples/tide/src/main.rs +++ b/examples/tide/src/main.rs @@ -18,7 +18,7 @@ use tide::{Next, Request, Response, StatusCode}; #[async_std::main] async fn main() -> Result<(), std::io::Error> { let mut app = tide::new(); - app.middleware(handle_error); + app.with(handle_error); app.at("/static/*path").get(static_file); app.at("/favicon.ico").get(favicon); app.at("/").get(frontpage); From 1f47f57f287ec4439dfb314200868a3c408be8cf Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Sun, 2 Aug 2020 16:45:50 +0200 Subject: [PATCH 7/9] Initial tide feature. So far, just the http-types feature through the tide reexport. --- Cargo.toml | 1 + examples/tide/Cargo.toml | 3 +-- src/lib.rs | 4 ++++ src/staticfiles.rs | 4 +++- 4 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 29b3445..d41c78f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ mime02 = [] mime03 = ["mime"] warp02 = ["mime03"] http-types = [] +tide = ["http-types"] [dependencies] base64 = "^0.12.0" diff --git a/examples/tide/Cargo.toml b/examples/tide/Cargo.toml index 13dec38..250391b 100644 --- a/examples/tide/Cargo.toml +++ b/examples/tide/Cargo.toml @@ -7,10 +7,9 @@ edition = "2018" build = "src/build.rs" [build-dependencies] -ructe = { path = "../..", features = ["http-types", "sass"] } +ructe = { path = "../..", features = ["tide", "sass"] } [dependencies] async-std = { version = "1.6.0", features = ["attributes"] } tide = "0.13.0" httpdate = "0.3.1" -http-types = "2.3.0" diff --git a/src/lib.rs b/src/lib.rs index 6237e87..d583047 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,10 +92,14 @@ //! simplify template rendering in the [warp] framework, versions 0.2.x. //! * `http-types` -- Static files know their mime types, compatible with //! the [http-types] crate. +//! * `tide` -- Support for the [tide] framework. Implies the +//! `http-types` feature (but does not require a direct http-types +//! requirement, as that is reexported by tide). //! //! [`response::Builder`]: ../http/response/struct.Builder.html //! [mime]: https://crates.rs/crates/mime //! [warp]: https://crates.rs/crates/warp +//! [tide]: https://crates.rs/crates/tide //! [http-types]: https://crates.rs/crates/http-types //! //! The `mime02`, `mime03`, and `http-types` features are mutually diff --git a/src/staticfiles.rs b/src/staticfiles.rs index 71e9ae1..1fdc230 100644 --- a/src/staticfiles.rs +++ b/src/staticfiles.rs @@ -208,7 +208,9 @@ impl StaticFiles { if cfg!(feature = "mime03") { src.write_all(b"extern crate mime;\nuse self::mime::Mime;\n\n")?; } - if cfg!(feature = "http-types") { + if cfg!(feature = "tide") { + src.write_all(b"use tide::http::mime::{self, Mime};\n\n")?; + } else if cfg!(feature = "http-types") { src.write_all(b"use http_types::mime::{self, Mime};\n\n")?; } src.write_all( From 08e293443582607c3c4d5ad2d3f0d65b63cdd020 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Thu, 6 Aug 2020 19:27:23 +0200 Subject: [PATCH 8/9] Add a version nr to tide feature. Since tide is still 0.x, it may be good to prepare for breaking changes. --- Cargo.toml | 2 +- examples/tide/Cargo.toml | 4 ++-- src/lib.rs | 6 +++--- src/staticfiles.rs | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index d41c78f..e87be8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,7 +16,7 @@ mime02 = [] mime03 = ["mime"] warp02 = ["mime03"] http-types = [] -tide = ["http-types"] +tide013 = ["http-types"] [dependencies] base64 = "^0.12.0" diff --git a/examples/tide/Cargo.toml b/examples/tide/Cargo.toml index 250391b..953e4c1 100644 --- a/examples/tide/Cargo.toml +++ b/examples/tide/Cargo.toml @@ -7,9 +7,9 @@ edition = "2018" build = "src/build.rs" [build-dependencies] -ructe = { path = "../..", features = ["tide", "sass"] } +ructe = { path = "../..", features = ["tide013", "sass"] } [dependencies] async-std = { version = "1.6.0", features = ["attributes"] } -tide = "0.13.0" +tide = "0.13.0" # Note: Feature flag for ructe matches this version httpdate = "0.3.1" diff --git a/src/lib.rs b/src/lib.rs index d583047..9199f04 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -92,9 +92,9 @@ //! simplify template rendering in the [warp] framework, versions 0.2.x. //! * `http-types` -- Static files know their mime types, compatible with //! the [http-types] crate. -//! * `tide` -- Support for the [tide] framework. Implies the -//! `http-types` feature (but does not require a direct http-types -//! requirement, as that is reexported by tide). +//! * `tide013` -- Support for the [tide] framework version 0.13.x. +//! Implies the `http-types` feature (but does not require a direct +//! http-types requirement, as that is reexported by tide). //! //! [`response::Builder`]: ../http/response/struct.Builder.html //! [mime]: https://crates.rs/crates/mime diff --git a/src/staticfiles.rs b/src/staticfiles.rs index 1fdc230..39d43cc 100644 --- a/src/staticfiles.rs +++ b/src/staticfiles.rs @@ -208,7 +208,7 @@ impl StaticFiles { if cfg!(feature = "mime03") { src.write_all(b"extern crate mime;\nuse self::mime::Mime;\n\n")?; } - if cfg!(feature = "tide") { + if cfg!(feature = "tide013") { src.write_all(b"use tide::http::mime::{self, Mime};\n\n")?; } else if cfg!(feature = "http-types") { src.write_all(b"use http_types::mime::{self, Mime};\n\n")?; From 07f5f1c6408c697d8a47f187cae9966c64431222 Mon Sep 17 00:00:00 2001 From: Rasmus Kaj Date: Thu, 6 Aug 2020 23:57:38 +0200 Subject: [PATCH 9/9] Add some documentation. --- examples/tide/src/ructe_tide.rs | 71 +++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/examples/tide/src/ructe_tide.rs b/examples/tide/src/ructe_tide.rs index c4e2885..1478e07 100644 --- a/examples/tide/src/ructe_tide.rs +++ b/examples/tide/src/ructe_tide.rs @@ -1,8 +1,47 @@ +//! These traits and impl may be included in the tide feature of ructe +//! in a future release. +//! +//! Comments welcome at +//! [kaj/ructe#79](https://github.com/kaj/ructe/issues/79). + +/// Add `render` and `render_html` methods to [`tide::Response`]. +/// +/// [`tide::Response`]: ../../tide/struct.Response.html pub trait Render { + /// Render a template to the body of self. + /// + /// The `Call` takes a `Write` target as only argument, other + /// arguments will typically be moved or borrowed into a closure + /// that is used as `call`. + /// + /// # Examples + /// + /// ``` + /// # use tide::Response; + /// # use ructe_tide::ructe_tide::Render; + /// # use std::io::{self, Write}; + /// # // Mock template: + /// # fn page(o: impl Write, c: &str, n: u8) -> io::Result<()> { + /// # Ok(()) + /// # } + /// # fn main() -> Result<(), Box> { + /// let content = "something"; + /// let other = 17; + /// let mut result = Response::new(200); + /// result.render(|o| page(o, content, other))?; + /// # Ok(()) + /// # } + /// ``` fn render(&mut self, call: Call) -> std::io::Result<()> where Call: FnOnce(&mut dyn std::io::Write) -> std::io::Result<()>; + /// Render a template to the html body of self. + /// + /// just like `render`, excep it also sets the content-type of the + /// respons to [`HTML`]. + /// + /// [`HTML`]: ../../tide/http/mime/constant.HTML.html fn render_html(&mut self, call: Call) -> std::io::Result<()> where Call: FnOnce(&mut dyn std::io::Write) -> std::io::Result<()>; @@ -29,11 +68,43 @@ impl Render for tide::Response { } } +/// Add `render` and `render_html` methods to [`tide::ResponseBuilder`]. +/// +/// [`tide::Response`]: ../../tide/struct.ResponseBuilder.html pub trait RenderBuilder { + /// Render a template to the body of self. + /// + /// The `Call` takes a `Write` target as only argument, other + /// arguments will typically be moved or borrowed into a closure + /// that is used as `call`. + /// + /// # Examples + /// + /// ``` + /// # use tide::Response; + /// # use ructe_tide::ructe_tide::RenderBuilder; + /// # use std::io::{self, Write}; + /// # // Mock template: + /// # fn page(o: impl Write, c: &str, n: u8) -> io::Result<()> { + /// # Ok(()) + /// # } + /// let content = "something"; + /// let other = 17; + /// Response::builder(200) + /// .render(|o| page(o, content, other)) + /// .build() + /// # ; + /// ``` fn render(self, call: Call) -> tide::ResponseBuilder where Call: FnOnce(&mut dyn std::io::Write) -> std::io::Result<()>; + /// Render a template to the html body of self. + /// + /// just like `render`, excep it also sets the content-type of the + /// respons to [`HTML`]. + /// + /// [`HTML`]: ../../tide/http/mime/constant.HTML.html fn render_html(self, call: Call) -> tide::ResponseBuilder where Call: FnOnce(&mut dyn std::io::Write) -> std::io::Result<()>;