From 55482f880cfa67e68e52e5937f36251e886c137c Mon Sep 17 00:00:00 2001 From: Philipp Korber Date: Thu, 22 Nov 2018 20:22:23 +0100 Subject: fix(examples) made template example usable again --- mail/Cargo.toml | 3 - .../templates/template_a/template.toml | 2 +- mail/examples/mail_from_template/error.rs | 56 --------- mail/examples/mail_from_template/main.rs | 129 ++++++++------------- mail/src/lib.rs | 13 ++- template/src/handlebars.rs | 27 +++++ template/src/lib.rs | 3 + template/src/serde_impl.rs | 72 +++++++++--- 8 files changed, 145 insertions(+), 160 deletions(-) delete mode 100644 mail/examples/mail_from_template/error.rs diff --git a/mail/Cargo.toml b/mail/Cargo.toml index 2d55b36..0a5f504 100644 --- a/mail/Cargo.toml +++ b/mail/Cargo.toml @@ -20,18 +20,15 @@ test-utils = ["mail-core/test-utils"] [[example]] name = "mail_by_hand" -crate-type = ["bin"] path = "examples/mail_by_hand.rs" [[example]] name = "mail_from_template" -crate-type = ["bin"] path = "examples/mail_from_template/main.rs" required-features = ["handlebars"] [[example]] name = "send_mail" -create-type = ["bind"] path = "examples/send_mail/main.rs" required-features = ["smtp"] diff --git a/mail/example_resources/templates/template_a/template.toml b/mail/example_resources/templates/template_a/template.toml index 4da0f10..25ff85a 100644 --- a/mail/example_resources/templates/template_a/template.toml +++ b/mail/example_resources/templates/template_a/template.toml @@ -11,6 +11,6 @@ path = "mail.html" logo = "logo.png" [[attachments]] -iri = "path:./portfolio.pdf" +iri = "path:portfolio.pdf" media_type = "application/pdf" diff --git a/mail/examples/mail_from_template/error.rs b/mail/examples/mail_from_template/error.rs deleted file mode 100644 index 21bccfe..0000000 --- a/mail/examples/mail_from_template/error.rs +++ /dev/null @@ -1,56 +0,0 @@ - -use mail::error::{CompositionError, ComponentCreationError}; -use mail::render_template_engine::error::{CreatingSpecError, InsertionError as _InsertionError}; -use mail::tera::error::TeraError; - -type InsertionError = _InsertionError; - -#[derive(Fail, Debug)] -pub enum SetupError { - #[fail(display = "{}", _0)] - Tera(TeraError), - - #[fail(display = "{}", _0)] - CreatingSpecs(CreatingSpecError), - - #[fail(display = "{}", _0)] - UsingSpecs(InsertionError) -} - -impl From for SetupError { - fn from(err: TeraError) -> Self { - SetupError::Tera(err) - } -} -impl From for SetupError { - fn from(err: InsertionError) -> Self { - SetupError::UsingSpecs(err) - } -} - -impl From for SetupError { - fn from(err: CreatingSpecError) -> Self { - SetupError::CreatingSpecs(err) - } -} - -// TODO `Header` should be mergeable into `Composition`. -#[derive(Fail, Debug)] -pub enum Error { - #[fail(display = "{}", _0)] - Composition(CompositionError), - #[fail(display = "{}", _0)] - Header(ComponentCreationError) -} - -impl From> for Error { - fn from(err: CompositionError) -> Self { - Error::Composition(err) - } -} - -impl From for Error { - fn from(err: ComponentCreationError) -> Self { - Error::Header(err) - } -} diff --git a/mail/examples/mail_from_template/main.rs b/mail/examples/mail_from_template/main.rs index 6a43125..deeda80 100644 --- a/mail/examples/mail_from_template/main.rs +++ b/mail/examples/mail_from_template/main.rs @@ -1,99 +1,70 @@ -//! In this example a mail is created using the tempalte engine with tera bindings +//! In this example a mail is created using the template engine with handlebars bindings //! (and then printed) //! -#[cfg(not(all(feature = "render-template-engine", feature = "tera-engine")))] -compile_error!("example `mail_from_template` requires feature `render-template-engine` and `tera-engine`"); -#[macro_use] extern crate mail; -#[macro_use] -extern crate failure; -#[macro_use] -extern crate serde_derive; extern crate futures; extern crate soft_ascii_string; +extern crate serde; +extern crate failure; + +use std::{ + path::Path +}; +use failure::Error; use futures::Future; use soft_ascii_string::SoftAsciiString; -use std::str; -use std::borrow::Cow; - -use mail::error::MailError; -use mail::{Mail, Mailbox, MailType, HeaderTryFrom, Context, Domain}; -use mail::default_impl::simple_context; -use mail::template::{InspectEmbeddedResources, Embedded, MailSendData}; -use mail::render_template_engine::{RenderTemplateEngine, TemplateSpec, DEFAULT_SETTINGS}; -use mail::tera::TeraRenderEngine; - -use self::error::{SetupError, Error}; - -mod error; - -#[derive(Debug, Serialize, InspectEmbeddedResources)] +use serde::Serialize; + +use mail::{ + Domain, + MailType, + default_impl::simple_context, + template::{ + load_toml_template_from_path, + handlebars::Handlebars, + TemplateExt + }, + headers, + HeaderTryFrom, + HeaderKind +}; + +#[derive(Debug, Serialize)] struct UserData { name: &'static str - // TODO: include embedded avatar. } fn main() { + //TODO[workspace] check if [dir mail and has sub-dir mail]/[dir mail and has parent mail] + // depending on this switch the CWD to the subdir-mail + eprintln!(concat!( + "Warning: This example currently only works if mail/mail is the cwd\n", + " i.e. it fails when run from the workspace!" + )); let msg_id_domain = Domain::try_from("company_a.test").unwrap(); let unique_part = SoftAsciiString::from_string("c207n521cec").unwrap(); let ctx = simple_context::new(msg_id_domain, unique_part).unwrap(); - let engine = setup_template_engine().unwrap(); - - let mail = create_mail(&engine, &ctx).unwrap(); - let string_mail = encode_mail_to_string(mail, ctx.clone()).unwrap(); - println!("{}", string_mail); -} - -fn create_mail(engine: &RenderTemplateEngine, ctx: &impl Context) - -> Result -{ - let from = Mailbox::try_from("a@b.c")?; - let to = Mailbox::try_from("d@e.f")?; - let subject = "Live might not be a roller coaster"; - // Use template_a. - let template_id = Cow::Borrowed("template_a"); - // This can be basically a type implementing `Serialize`, - // in the tera template we use it with `{{data.name}}`. - let data = UserData { name: "Mr. Slow Coaster" }; - - // `MailSendData` contains everything needed to crate (and send) - // a mail based on a template. - let send_data = MailSendData::simple_new( - from, to, subject, - template_id, data - ); - - let mail = send_data.compose(ctx, engine)?; - Ok(mail) -} - -fn setup_template_engine() -> Result, SetupError> { - // Create instance of the tera rte implementation, - // we can reuse/derive from all templates in `tera_base` with - // this (and we do so in the example using `{% extends "base_mail.html" %}`). - let tera = TeraRenderEngine::new("./example_resources/tera_base/**/*")?; - let mut rte = RenderTemplateEngine::new(tera); - // Load all template specs based on the files/folders in `templates` - // using the folder structure as way to define the templates is easy - // but we can do differently if we need to. - let specs = TemplateSpec - ::from_dirs("./example_resources/templates", &*DEFAULT_SETTINGS)?; - - for (name, spec) in specs { - rte.insert_spec(name, spec)?; - } - Ok(rte) -} - -fn encode_mail_to_string(mail: Mail, ctx: impl Context) -> Result { - let res = mail - .into_encodeable_mail(ctx) - .wait()? - .encode_into_bytes(MailType::Ascii)?; - - Ok(String::from_utf8(res).unwrap()) + let engine = Handlebars::new(); + let template_path = Path::new("example_resources/templates/template_a/template.toml").to_owned(); + let fut = load_toml_template_from_path(engine, template_path, &ctx) + .and_then(|template| -> Result<_, Error> { + // If our "user data" would contain additional inline_emebeddings/attachments + // we would need another "and_then" chain here where we load such parts. + let mut mail = template.render(UserData { name: "Ferris" }.into(), &ctx)?; + // `auto_body` has some nice but failable conversion for **header bodies**. + // Alternatively there is `::body()` which + // is not failable. + mail.insert_header(headers::_From::auto_body(["a@b.example"])?); + mail.insert_header(headers::_To::auto_body(["d@e.example"])?); + Ok(mail) + }).and_then(|mail| mail.into_encodable_mail(ctx.clone()).map_err(Into::into)); + + let enc_mail = fut.wait().unwrap(); + let bytes = enc_mail.encode_into_bytes(MailType::Ascii).unwrap(); + let string = String::from_utf8(bytes).unwrap(); + println!("{}", string); } diff --git a/mail/src/lib.rs b/mail/src/lib.rs index dd0eeb7..39a3c8e 100644 --- a/mail/src/lib.rs +++ b/mail/src/lib.rs @@ -98,7 +98,7 @@ //! which normally doesn't need to be accessed directly. #[doc(hidden)] -pub extern crate mail_internals; +pub extern crate mail_internals as internals; extern crate mail_headers; extern crate mail_core; pub extern crate mail_template as template; @@ -120,7 +120,7 @@ pub use mail_core::*; /// - `mail-template` /// - `mail-smtp` (feature: `smtp`) pub mod error { - pub use mail_internals::error::*; + pub use crate::internals::error::*; pub use mail_headers::error::*; pub use mail_headers::InvalidHeaderName; pub use mail_core::error::*; @@ -129,7 +129,7 @@ pub mod error { pub use smtp::error::*; } -pub use mail_internals::MailType; +pub use self::internals::MailType; /// Re-export of headers from `mail-headers`. pub use mail_headers::{ @@ -154,6 +154,9 @@ pub use mail_headers::{ def_headers, }; +#[doc(hidden)] +pub use mail_headers::data; + pub use self::header_components::{ MediaType, Mailbox, @@ -162,8 +165,8 @@ pub use self::header_components::{ }; -#[doc(hidden)] -pub use mail_headers::data; +// Re-export some parts of mail-internals useful for writing custom header. +pub use crate::internals::{encoder as header_encoding}; /// Re-export of the default_impl parts from `mail-core`. /// diff --git a/template/src/handlebars.rs b/template/src/handlebars.rs index 2d06a31..370e649 100644 --- a/template/src/handlebars.rs +++ b/template/src/handlebars.rs @@ -11,6 +11,14 @@ use super::{ serde_impl }; +//TODO[FEAT] add custom engine config section to loading +// e.g. something like: +// ``` +// [engine] +// load_partial = "../partials/baseh.html" +// ``` +// +// Just specific to each engine. pub struct Handlebars { @@ -20,6 +28,25 @@ pub struct Handlebars { impl Handlebars { + pub fn new() -> Self { + Handlebars { + inner: hbs::Handlebars::new(), + name_counter: 0 + } + } + + pub fn inner(&self) -> &hbs::Handlebars { + &self.inner + } + + /// Provides mutable access to the underling handlebars instance. + /// + /// This can be used to e.g. add partials (in the future the template + /// file will have a custom config section but currently it doesn't). + pub fn inner_mut(&mut self) -> &mut hbs::Handlebars { + &mut self.inner + } + fn next_body_template_name(&mut self) -> String { let name = format!("body_{}", self.name_counter); self.name_counter += 1; diff --git a/template/src/lib.rs b/template/src/lib.rs index 09aea12..26e70cb 100644 --- a/template/src/lib.rs +++ b/template/src/lib.rs @@ -87,6 +87,9 @@ pub trait TemplateEngineCanHandleData: TemplateEngine { } /// Load a template as described in a toml file. +/// +/// This will set the default of the base_dir to the +/// dir the template file loaded is in. pub fn load_toml_template_from_path( engine: TE, path: PathBuf, diff --git a/template/src/serde_impl.rs b/template/src/serde_impl.rs index 2ab709d..f8e7021 100644 --- a/template/src/serde_impl.rs +++ b/template/src/serde_impl.rs @@ -1,13 +1,12 @@ use std::{ collections::HashMap, - path::{Path, PathBuf} + path::{Path, PathBuf}, + mem }; use serde::{ Serialize, Deserialize, - de::{ - Deserializer, - }, + Serializer, Deserializer }; use failure::Error; use futures::{Future, future::{self, Either}}; @@ -52,10 +51,10 @@ pub struct TemplateBase { base_dir: Option, subject: LazySubject, bodies: Vec1, - //TODO impl. deserialize where - // resource:String -> IRI::new("path", resource) -> Resource::Source + #[serde(default)] #[serde(deserialize_with="deserialize_embeddings")] embeddings: HashMap, + #[serde(default)] #[serde(deserialize_with="deserialize_attachments")] attachments: Vec, } @@ -65,7 +64,7 @@ impl TemplateBase { //TODO!! make this load all embeddings/attachments and make it a future - /// Couples the template base with a specific engine instance. + /// Couples the template base with a specific engine instance.`` pub fn load(self, mut engine: TE, default_base_dir: CwdBaseDir, ctx: &impl Context) -> impl Future, Error=Error> { let TemplateBase { template_name, @@ -98,18 +97,31 @@ impl TemplateBase Ok((subject, bodies)) })(); - let (subject, bodies) = + let (subject, mut bodies) = match catch_res { Ok(vals) => vals, Err(err) => { return Either::B(future::err(err)); } }; - let loading_fut = Resource::load_container(embeddings, ctx) - .join(Resource::load_container(attachments, ctx)); + let loading_embeddings = Resource::load_container(embeddings, ctx); + let loading_attachments = Resource::load_container(attachments, ctx); + let loading_body_embeddings = bodies.iter_mut() + .map(|body| { + //Note: empty HashMap does not alloc! + let body_embeddings = mem::replace(&mut body.inline_embeddings, HashMap::new()); + Resource::load_container(body_embeddings, ctx) + }) + .collect::>(); + let loading_body_embeddings = future::join_all(loading_body_embeddings); + - let fut = loading_fut + let fut = loading_embeddings + .join3(loading_attachments, loading_body_embeddings) .map_err(Error::from) - .map(|(embeddings, attachments)| { + .map(|(embeddings, attachments, body_embeddings)| { + for (body, loaded_embeddings) in bodies.iter_mut().zip(body_embeddings) { + mem::replace(&mut body.inline_embeddings, loaded_embeddings); + } Template { template_name, base_dir, @@ -125,12 +137,30 @@ impl TemplateBase } } -#[derive(Debug, Serialize, Deserialize)] +#[derive(Debug)] struct LazySubject { - #[serde(flatten)] template_string: String } +impl Serialize for LazySubject { + + fn serialize(&self, serializer: S) -> Result + where S: Serializer + { + serializer.serialize_str(&self.template_string) + } +} + +impl<'de> Deserialize<'de> for LazySubject { + + fn deserialize(deserializer: D) -> Result + where D: Deserializer<'de> + { + let template_string = String::deserialize(deserializer)?; + Ok(LazySubject { template_string }) + } +} + #[derive(Deserialize)] #[serde(untagged)] enum ResourceDeserializationHelper { @@ -214,13 +244,23 @@ impl PathRebaseable for StandardLazyBodyTemplate { fn rebase_to_include_base_dir(&mut self, base_dir: impl AsRef) -> Result<(), UnsupportedPathError> { - self.path.rebase_to_include_base_dir(base_dir) + let base_dir = base_dir.as_ref(); + self.path.rebase_to_include_base_dir(base_dir)?; + for embedding in self.embeddings.values_mut() { + embedding.rebase_to_include_base_dir(base_dir)?; + } + Ok(()) } fn rebase_to_exclude_base_dir(&mut self, base_dir: impl AsRef) -> Result<(), UnsupportedPathError> { - self.path.rebase_to_exclude_base_dir(base_dir) + let base_dir = base_dir.as_ref(); + self.path.rebase_to_exclude_base_dir(base_dir)?; + for embedding in self.embeddings.values_mut() { + embedding.rebase_to_exclude_base_dir(base_dir)?; + } + Ok(()) } } -- cgit v1.2.3