1extern crate failure;
2extern crate serde;
3extern crate futures;
4extern crate mail_core;
5extern crate mail_headers;
6extern crate vec1;
7extern crate toml;
8extern crate maybe_owned;
9#[cfg(feature="handlebars")]
10extern crate handlebars as hbs;
11
12#[cfg(all(feature="handlebars", not(feature="handlebars-bindings")))]
13compile_error!("use feature `handlebars-bindings` instead of opt-dep-auto-feature `handlebars`");
14
15use std::{
16 fs,
17 collections::HashMap,
18 fmt::Debug,
19 path::{Path, PathBuf},
20 ops::Deref
21};
22
23use serde::{
24 Serialize,
25 Deserialize
26};
27use failure::Error;
28use futures::{
29 Future, Poll, Async,
30 try_ready,
31 future::{self, Join, Either}
32};
33use vec1::Vec1;
34use maybe_owned::MaybeOwned;
35
36use mail_core::{
37 Resource,
38 Data, Metadata,
39 Context, ResourceContainerLoadingFuture,
40 compose::{MailParts, BodyPart},
41 Mail
42};
43use mail_headers::{
44 HeaderKind, Header,
45 header_components::MediaType,
46 headers
47};
48
49mod base_dir;
50mod path_rebase;
51mod additional_cid;
52pub mod serde_impl;
53pub mod error;
54
55#[cfg(feature="handlebars")]
56pub mod handlebars;
57
58pub use self::base_dir::*;
59pub use self::path_rebase::*;
60pub use self::additional_cid::*;
61
62pub trait TemplateEngine: Sized {
64 type Id: Debug;
65
66 type LazyBodyTemplate: PathRebaseable + Debug + Send + Serialize + for<'a> Deserialize<'a>;
67
68 fn load_body_template(&mut self, tmpl: Self::LazyBodyTemplate)
69 -> Result<BodyTemplate<Self>, Error>;
70
71 fn load_subject_template(&mut self, template_string: String)
72 -> Result<Self::Id, Error>;
73}
74
75pub trait TemplateEngineCanHandleData<D>: TemplateEngine {
80
81 fn render<'r, 'a>(
82 &'r self,
83 id: &'r Self::Id,
84 data: &'r D,
85 additional_cids: AdditionalCIds<'r>
86 ) -> Result<String, Error>;
87}
88
89pub fn load_toml_template_from_path<TE, C>(
94 engine: TE,
95 path: PathBuf,
96 ctx: &C
97) -> impl Future<Item=Template<TE>, Error=Error>
98 where TE: TemplateEngine + 'static, C: Context
99{
100
101 let ctx2 = ctx.clone();
102 ctx.offload_fn(move || {
103 let content = fs::read_to_string(&path)?;
104 let base: serde_impl::TemplateBase<TE> = toml::from_str(&content)?;
105 let base_dir = path.parent().unwrap_or_else(||Path::new("."));
106 let base_dir = CwdBaseDir::from_path(base_dir)?;
107 Ok((base, base_dir))
108 }).and_then(move |(base, base_dir)| base.load(engine, base_dir, &ctx2))
109}
110
111pub fn load_toml_template_from_str<TE, C>(
113 engine: TE,
114 content: &str,
115 ctx: &C
116) -> impl Future<Item=Template<TE>, Error=Error>
117 where TE: TemplateEngine, C: Context
118{
119 let base: serde_impl::TemplateBase<TE> =
120 match toml::from_str(content) {
121 Ok(base) => base,
122 Err(err) => { return Either::B(future::err(Error::from(err))); }
123 };
124
125 let base_dir =
126 match CwdBaseDir::from_path(Path::new(".")) {
127 Ok(base_dir) => base_dir,
128 Err(err) => { return Either::B(future::err(Error::from(err))) }
129 };
130
131 Either::A(base.load(engine, base_dir, ctx))
132}
133
134
135#[derive(Debug)]
137pub struct Template<TE: TemplateEngine> {
138 template_name: String,
139 base_dir: CwdBaseDir,
140 subject: Subject<TE>,
141 bodies: Vec1<BodyTemplate<TE>>,
145 embeddings: HashMap<String, Resource>,
147 attachments: Vec<Resource>,
148 engine: TE,
149}
150
151impl<TE> Template<TE>
152 where TE: TemplateEngine
153{
154 pub fn inline_embeddings(&self) -> &HashMap<String, Resource> {
155 &self.embeddings
156 }
157
158 pub fn attachments(&self) -> &[Resource] {
159 &self.attachments
160 }
161
162 pub fn engine(&self) -> &TE {
163 &self.engine
164 }
165
166 pub fn bodies(&self) -> &[BodyTemplate<TE>] {
167 &self.bodies
168 }
169
170 pub fn subject_template_id(&self) -> &TE::Id {
171 &self.subject.template_id
172 }
173}
174
175
176#[derive(Debug)]
178pub struct BodyTemplate<TE: TemplateEngine> {
179 pub template_id: TE::Id,
180 pub media_type: MediaType,
181 pub inline_embeddings: HashMap<String, Resource>
182}
183
184impl<TE> BodyTemplate<TE>
185 where TE: TemplateEngine
186{
187 pub fn template_id(&self) -> &TE::Id {
188 &self.template_id
189 }
190
191 pub fn media_type(&self) -> &MediaType {
192 &self.media_type
193 }
194
195 pub fn inline_embeddings(&self) -> &HashMap<String, Resource> {
196 &self.inline_embeddings
197 }
198}
199
200#[derive(Debug)]
202pub struct Subject<TE: TemplateEngine> {
203 template_id: TE::Id
204}
205
206impl<TE> Subject<TE>
207 where TE: TemplateEngine
208{
209 pub fn template_id(&self) -> &TE::Id {
210 &self.template_id
211 }
212}
213
214pub trait TemplateExt<TE, D>
221 where TE: TemplateEngine + TemplateEngineCanHandleData<D>
222{
223
224 fn render_to_mail_parts<'r>(&self, data: LoadedTemplateData<'r, D>, ctx: &impl Context)
225 -> Result<(MailParts, Header<headers::Subject>), Error>;
226
227 fn render<'r>(&self, data: LoadedTemplateData<'r, D>, ctx: &impl Context) -> Result<Mail, Error> {
228 let (parts, subject) = self.render_to_mail_parts(data, ctx)?;
229 let mut mail = parts.compose();
230 mail.insert_header(subject);
231 Ok(mail)
232 }
233}
234
235
236impl<TE, D> TemplateExt<TE, D> for Template<TE>
237 where TE: TemplateEngine + TemplateEngineCanHandleData<D>
238{
239 fn render_to_mail_parts<'r>(&self, data: LoadedTemplateData<'r, D>, ctx: &impl Context)
240 -> Result<(MailParts, Header<headers::Subject>), Error>
241 {
242 let TemplateData {
243 data,
244 inline_embeddings,
245 mut attachments
246 } = data.into();
247
248 let subject = self.engine().render(
249 self.subject_template_id(),
250 &data,
251 AdditionalCIds::new(&[])
252 )?;
253
254 let subject = headers::Subject::auto_body(subject)?;
255
256 let mut bodies = Vec::new();
258 for body in self.bodies().iter() {
259 let raw = self.engine().render(
260 body.template_id(),
261 &data,
262 AdditionalCIds::new(&[
263 &inline_embeddings,
264 body.inline_embeddings(),
265 self.inline_embeddings()
266 ])
267 )?;
268
269 let data = Data::new(
270 raw.into_bytes(),
271 Metadata {
272 file_meta: Default::default(),
273 media_type: body.media_type().clone(),
274 content_id: ctx.generate_content_id()
275 }
276 );
277
278 let inline_embeddings = body.inline_embeddings()
279 .values()
280 .cloned()
281 .collect();
282
283 bodies.push(BodyPart {
284 resource: Resource::Data(data),
285 inline_embeddings,
286 attachments: Vec::new()
287 });
288 }
289
290 attachments.extend(self.attachments().iter().cloned());
291
292 let mut inline_embeddings_vec = Vec::new();
293 for (key, val) in self.inline_embeddings() {
294 if !inline_embeddings.contains_key(key) {
295 inline_embeddings_vec.push(val.clone())
296 }
297 }
298
299 inline_embeddings_vec.extend(inline_embeddings.into_iter().map(|(_,v)|v));
300
301 let parts = MailParts {
302 alternative_bodies: Vec1::try_from_vec(bodies).unwrap(),
304 inline_embeddings: inline_embeddings_vec,
305 attachments
306 };
307
308 Ok((parts, subject))
309 }
310}
311
312pub struct TemplateData<'a, D: 'a> {
313 pub data: MaybeOwned<'a, D>,
314 pub attachments: Vec<Resource>,
315 pub inline_embeddings: HashMap<String, Resource>
316}
317
318impl<'a, D> TemplateData<'a, D> {
319
320 pub fn load(self, ctx: &impl Context) -> DataLoadingFuture<'a, D> {
321 let TemplateData {
322 data,
323 attachments,
324 inline_embeddings
325 } = self;
326
327 let loading_fut = Resource::load_container(inline_embeddings, ctx)
328 .join(Resource::load_container(attachments, ctx));
329
330 DataLoadingFuture {
331 payload: Some(data),
332 loading_fut
333 }
334 }
335}
336impl<D> From<D> for TemplateData<'static, D> {
337 fn from(data: D) -> Self {
338 TemplateData {
339 data: data.into(),
340 attachments: Default::default(),
341 inline_embeddings: Default::default()
342 }
343 }
344}
345
346impl<'a, D> From<&'a D> for TemplateData<'a, D> {
347 fn from(data: &'a D) -> Self {
348 TemplateData {
349 data: data.into(),
350 attachments: Default::default(),
351 inline_embeddings: Default::default()
352 }
353 }
354}
355
356pub struct LoadedTemplateData<'a, D: 'a>(TemplateData<'a, D>);
357
358impl<'a, D> From<&'a D> for LoadedTemplateData<'a, D> {
359 fn from(data: &'a D) -> Self {
360 LoadedTemplateData(TemplateData::from(data))
361 }
362}
363
364impl<D> From<D> for LoadedTemplateData<'static, D> {
365 fn from(data: D) -> Self {
366 LoadedTemplateData(TemplateData::from(data))
367 }
368}
369
370impl<'a, D> Deref for LoadedTemplateData<'a, D> {
371 type Target = TemplateData<'a, D>;
372
373 fn deref(&self) -> &Self::Target {
374 &self.0
375 }
376}
377
378impl<'a, D> Into<TemplateData<'a, D>> for LoadedTemplateData<'a, D> {
379 fn into(self) -> TemplateData<'a, D> {
380 let LoadedTemplateData(data) = self;
381 data
382 }
383}
384
385pub struct DataLoadingFuture<'a, D: 'a> {
387 payload: Option<MaybeOwned<'a, D>>,
388 loading_fut: Join<
389 ResourceContainerLoadingFuture<HashMap<String, Resource>>,
390 ResourceContainerLoadingFuture<Vec<Resource>>
391 >
392}
393
394impl<'a, D> Future for DataLoadingFuture<'a, D> {
395 type Item = LoadedTemplateData<'a, D>;
396 type Error = Error;
397
398 fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
399 let (
400 inline_embeddings,
401 attachments
402 ) = try_ready!(self.loading_fut.poll());
403
404 let data = self.payload.take().unwrap();
406
407 let inner = TemplateData {
408 data,
409 inline_embeddings,
410 attachments
411 };
412
413 Ok(Async::Ready(LoadedTemplateData(inner)))
414 }
415}