summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorPhilipp Korber <philippkorber@gmail.com>2018-11-22 20:22:23 +0100
committerPhilipp Korber <philippkorber@gmail.com>2018-11-22 20:22:23 +0100
commit55482f880cfa67e68e52e5937f36251e886c137c (patch)
tree6092677fac3b5422433188b99b098a66d029f563
parente0cdb9dbae5649b29753232bed9136389b940e23 (diff)
fix(examples) made template example usable again
-rw-r--r--mail/Cargo.toml3
-rw-r--r--mail/example_resources/templates/template_a/template.toml2
-rw-r--r--mail/examples/mail_from_template/error.rs56
-rw-r--r--mail/examples/mail_from_template/main.rs129
-rw-r--r--mail/src/lib.rs13
-rw-r--r--template/src/handlebars.rs27
-rw-r--r--template/src/lib.rs3
-rw-r--r--template/src/serde_impl.rs72
8 files changed, 145 insertions, 160 deletions
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<TeraError>;
-
-#[derive(Fail, Debug)]
-pub enum SetupError {
- #[fail(display = "{}", _0)]
- Tera(TeraError),
-
- #[fail(display = "{}", _0)]
- CreatingSpecs(CreatingSpecError),
-
- #[fail(display = "{}", _0)]
- UsingSpecs(InsertionError)
-}
-
-impl From<TeraError> for SetupError {
- fn from(err: TeraError) -> Self {
- SetupError::Tera(err)
- }
-}
-impl From<InsertionError> for SetupError {
- fn from(err: InsertionError) -> Self {
- SetupError::UsingSpecs(err)
- }
-}
-
-impl From<CreatingSpecError> 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<TeraError>),
- #[fail(display = "{}", _0)]
- Header(ComponentCreationError)
-}
-
-impl From<CompositionError<TeraError>> for Error {
- fn from(err: CompositionError<TeraError>) -> Self {
- Error::Composition(err)
- }
-}
-
-impl From<ComponentCreationError> 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<TeraRenderEngine>, ctx: &impl Context)
- -> Result<Mail, Error>
-{
- 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<RenderTemplateEngine<TeraRenderEngine>, 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<String, MailError> {
- 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 `<HeaderKind>::body(<SomeHeaderBodyType>)` 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<D>: 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<TE, C>(
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<TE: TemplateEngine> {
base_dir: Option<CwdBaseDir>,
subject: LazySubject,
bodies: Vec1<TE::LazyBodyTemplate>,
- //TODO impl. deserialize where
- // resource:String -> IRI::new("path", resource) -> Resource::Source
+ #[serde(default)]
#[serde(deserialize_with="deserialize_embeddings")]
embeddings: HashMap<String, Resource>,
+ #[serde(default)]
#[serde(deserialize_with="deserialize_attachments")]
attachments: Vec<Resource>,
}
@@ -65,7 +64,7 @@ impl<TE> TemplateBase<TE>
{
//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<Item=Template<TE>, Error=Error> {
let TemplateBase {
template_name,
@@ -98,18 +97,31 @@ impl<TE> TemplateBase<TE>
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::<Vec<_>>();
+ 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<TE> TemplateBase<TE>
}
}
-#[derive(Debug, Serialize, Deserialize)]
+#[derive(Debug)]
struct LazySubject {
- #[serde(flatten)]
template_string: String
}
+impl Serialize for LazySubject {
+
+ fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
+ where S: Serializer
+ {
+ serializer.serialize_str(&self.template_string)
+ }
+}
+
+impl<'de> Deserialize<'de> for LazySubject {
+
+ fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
+ 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<Path>)
-> 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<Path>)
-> 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(())
}
}