diff options
author | Philipp Korber <philippkorber@gmail.com> | 2018-05-30 18:28:03 +0200 |
---|---|---|
committer | Philipp Korber <philippkorber@gmail.com> | 2018-05-30 18:54:01 +0200 |
commit | f294689ea38d69ecb8c5a122682e3e65689d2eb6 (patch) | |
tree | c83915ff8dcffc7197205df538421d41e21d148c | |
parent | 40a6c11c4a4860af62f584e4b314f389b7894fb3 (diff) |
chore(api): moved render_template_engine and tera into different crate
-rw-r--r-- | Cargo.toml | 8 | ||||
-rw-r--r-- | notes/notes.md | 53 | ||||
-rw-r--r-- | src/lib.rs | 17 | ||||
-rw-r--r-- | src/render_template_engine/error.rs | 219 | ||||
-rw-r--r-- | src/render_template_engine/from_dir.rs | 141 | ||||
-rw-r--r-- | src/render_template_engine/mod.rs | 389 | ||||
-rw-r--r-- | src/render_template_engine/settings.rs | 296 | ||||
-rw-r--r-- | src/render_template_engine/utils.rs | 291 | ||||
-rw-r--r-- | src/resource/impl_inspect.rs | 79 | ||||
-rw-r--r-- | src/resource/mod.rs | 52 | ||||
-rw-r--r-- | src/template_engine.rs | 2 | ||||
-rw-r--r-- | src/tera/error.rs | 44 | ||||
-rw-r--r-- | src/tera/mod.rs | 101 | ||||
-rw-r--r-- | tests/rte/main.rs | 51 | ||||
-rw-r--r-- | tests/tera/main.rs | 114 |
15 files changed, 161 insertions, 1696 deletions
@@ -25,10 +25,7 @@ chrono = "0.4.0" vec1 = { git="https://github.com/1aim/vec1" } soft-ascii-string = "0.2.0" -futures-cpupool = { version = "0.1.5", optional = true } -tera = { version = "0.11.1", optional = true } -conduit-mime-types = { version = "0.7.3", optional = true} -lazy_static = { version = "1.0.0", optional = true } + [dependencies.mime] git="https://github.com/1aim/mime" @@ -41,7 +38,4 @@ serde_json = "1.0.2" regex = "0.2.6" [features] -debug_trace_tokens = [] -render-template-engine = ["conduit-mime-types", "lazy_static"] -tera-bindings = ["tera", "render-template-engine"] default = []
\ No newline at end of file diff --git a/notes/notes.md b/notes/notes.md index 7f0ebac..a6d68ea 100644 --- a/notes/notes.md +++ b/notes/notes.md @@ -12,7 +12,7 @@ where recipients_data is a iterable mapping from address to recipient specific d e.g. `Vec<(Address, Data)>` and mail_gen is something like `trait MailGen { fn gen_mail( from, to, data, bits8support ) -> MailBody; }` - + `MailBody` is not `tokio_smtp::MailBody` but has to implement nessesray contraints, (e.g. implemnting `toki_smtp::IntoMailBody` not that for the beginning this will be hard encoded but later one a generic variation allowing `smtp` to be switched out @@ -22,7 +22,7 @@ MailGen implementations are not done by hand but implemented ontop of something like a template spec e.g. `struct TemplateSpec { id_template: TemplateId, additional_appendixes: Vec<Appendix> }` Where `TemplateId` can is e.g. `reset_link` leading to the creation of a `html` with alternate `plain` -mail iff there is a `reset_link.html` and a `reset_link.plain` template. A `reset_link.html.data` +mail iff there is a `reset_link.html` and a `reset_link.plain` template. A `reset_link.html.data` folder could be used to define inline (mime related) appendixes like embedded images, but we might want to have a way to define such embeddigns through the data ( E.g. by mapping `Data => TemplateEnginData` and replacing `EmbeddedFile` variations @@ -57,11 +57,11 @@ MUST NOT occur in body (except for newline) - there is a `domain-literal` version which does use somthing like `[some_thing]`, we can use puny code for converting domains into ascii but probably can't use this with `domain-literal`'s - + - `local-part` is `dot-atom` which has leading and trailing `[CFWS]` so comments are alowed - MessageId uses a email address like syntax but without supporting spaces/comments - + # MIME @@ -112,17 +112,17 @@ Contend types: - `form-data` - `x-mixed-replace` (for server push, don't use by now there are better ways) - `byteranges` - + Example mail structure: ``` -(multipart/mixed +(multipart/mixed (multipart/alternative - (text/plain ... ) - (multipart/related - (text/hmtl ... '<img src="cid:contentid@1aim.com"></img>' ... ) - (image/png (Content-ID <contentid@1aim.com>) ... ) + (text/plain ... ) + (multipart/related + (text/hmtl ... '<img src="cid:ContentId@1aim.com"></img>' ... ) + (image/png (Content-ID <ContentId@1aim.com>) ... ) ... )) (image/png (Content-Disposition attachment) ...) (image/png (Content-Disposition attachment) ...)) @@ -131,15 +131,15 @@ Example mail structure: Possible alternate structure: ``` -(multipart/mixed +(multipart/mixed (multipart/related - + (multipart/alternative - (text/plain ... '[cid:contentid@1aim.com]' ... ) - (text/html ... '<img src="cid:contentid@1aim.com"></img>' ... ) ) - - (image/png (Content-ID <contentid@1aim.com>) ... ) ) - + (text/plain ... '[cid:ContentId@1aim.com]' ... ) + (text/html ... '<img src="cid:ContentId@1aim.com"></img>' ... ) ) + + (image/png (Content-ID <ContentId@1aim.com>) ... ) ) + (image/png (Content-Disposition attachment) ...) (image/png (Content-Disposition attachment) ...)) ``` @@ -156,7 +156,7 @@ proposed filenames for attachments can be given through parameters of the dispos it does not allow non ascii character there! see rfc2231 for more information, it extends some part wrt.: - + - splitting long parameters (e.g. long file names) - specifying language and character set - specifying language for encoded words @@ -173,7 +173,7 @@ a "big" text chunk can be split in multiple encoded words seperated by b'\r\n ' non encoded words and encoded words can apear in the same header field, but must be seperate by "linear-white-space" (space) which is NOT removed when -decoding encoded words +decoding encoded words encoded words can appear in: @@ -232,7 +232,7 @@ smtp still considers _the bytes_ corresponding to CR LF and DOT special. - there is a line length limit, lines terminate with b'CRLF' - b'.CRLF' does sill end the body (if preceeded by CRLF, or body starts with it) - so dot-staching is still done on protocol level - + ## Hot to handle `obs-` parsings @@ -290,7 +290,7 @@ therefore: warn when encoding a Disposition of kind Attachment which's -file_meta has no name set +file_meta has no name set // From RFC 2183: @@ -301,7 +301,7 @@ file_meta has no name set // be represented as `quoted-string'. Parameter values longer than 78 // characters, or which contain non-ASCII characters, MUST be encoded as // specified in [RFC 2184]. -provide a gnneral way for encoding header parameter which follow the scheme: +provide a gnneral way for encoding header parameter which follow the scheme: `<mainvalue> *(";" <key>"="<value> )` this are ContentType and ContentDisposition for now @@ -313,20 +313,19 @@ possible checking for "more" validity then noew email::quote => do not escape WSP, and use FWS when encoding also make quote, generally available for library useers a create_quoted_string( .. ) - + # Dependencies quoted_printable and base64 have some problems: 1. it's speaking of a 76 character limit where it is 78 it seems they treated the RFC as 78 character including CRLF where the RFC speaks of 78 characters EXCLUDING - CRLF + CRLF 2. it's only suited for content transfer encoding the body - as there is a limit of the length of encoded words (75) + as there is a limit of the length of encoded words (75) which can't be handled by both - + also quoted_printable has another problem: 3. in headers the number of character which can be displayed without encoding is more limited (e.g. no ' ' ) quoted_printable does not respect this? (TODO CHECK THIS) -
\ No newline at end of file @@ -15,13 +15,8 @@ extern crate soft_ascii_string; extern crate chrono; #[macro_use] extern crate vec1; -#[cfg(feature="render-template-engine")] -extern crate conduit_mime_types; -#[cfg(feature="render-template-engine")] -#[macro_use] -extern crate lazy_static; -#[cfg(feature="tera-bindings")] -extern crate tera as tera_crate; + + #[macro_use] #[allow(unused_imports)] extern crate mail_derive; @@ -38,14 +33,6 @@ mod template_engine; mod builder_extension; mod compositor; -#[cfg(feature="render-template-engine")] -pub mod render_template_engine; -#[cfg(feature="tera-bindings")] -pub mod tera; - -//TODO consider using glob reexports and pub(crate) for -// non public parts used by other modules - // reexports pub use self::builder_extension::BuilderExt; pub use self::compositor::*; diff --git a/src/render_template_engine/error.rs b/src/render_template_engine/error.rs deleted file mode 100644 index 6a0edbb..0000000 --- a/src/render_template_engine/error.rs +++ /dev/null @@ -1,219 +0,0 @@ -use std::fmt::{self, Display}; -use std::path::{Path, PathBuf}; -use std::ops::Deref; -use std::cmp::PartialEq; -use std::io; -use std::ffi::{OsStr, OsString}; - -use failure::{Fail, Backtrace, Context}; - -#[derive(Debug, Fail)] -pub enum Error<RE: Fail> { - #[fail(display = "unknown template id: {:?}", template_id)] - UnknownTemplateId { - template_id: String, - backtrace: Backtrace - }, - - #[fail(display = "{}", _0)] - RenderError(RE), -} - -impl<RE: Fail> Error<RE> { - - pub fn from_unknown_template_id<I>(tid: I) -> Self - where I: Into<String> - { - Error::UnknownTemplateId { - template_id: tid.into(), - backtrace: Backtrace::new() - } - } -} - -#[derive(Debug)] -pub struct LoadingSpecError { - inner: Context<LoadingSpecErrorVariant> -} - -impl LoadingSpecError { - - pub fn variant(&self) -> &LoadingSpecErrorVariant { - self.inner.get_context() - } -} -impl Fail for LoadingSpecError { - fn backtrace(&self) -> Option<&Backtrace> { - self.inner.backtrace() - } - - fn cause(&self) -> Option<&Fail> { - self.inner.cause() - } -} - -impl Display for LoadingSpecError { - fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result { - Display::fmt(&self.inner, fter) - } -} - -impl From<LoadingSpecErrorVariant>for LoadingSpecError { - fn from(variant: LoadingSpecErrorVariant) -> Self { - LoadingSpecError::from(Context::new(variant)) - } -} - -impl From<Context<LoadingSpecErrorVariant>> for LoadingSpecError { - fn from(inner: Context<LoadingSpecErrorVariant>) -> Self { - LoadingSpecError { inner } - } -} - -impl From<io::Error> for LoadingSpecError { - fn from(io_err: io::Error) -> Self { - io_err.context(LoadingSpecErrorVariant::IoError).into() - } -} - - - -#[derive(Debug, Fail)] -pub enum LoadingSpecErrorVariant { - - #[fail(display = "path must also be valid string, got: {}", _0)] - NonStringPath(DisplayPath), - - #[fail(display = "no type info in settings for: {:?}", type_name)] - MissingTypeInfo { type_name: String }, - - #[fail(display = "media type creation for body failed")] - BodyMediaTypeCreationFailure, - - #[fail(display = "media type creation for Embedding/Attachment failed")] - ResourceMediaTypeCreationFailure, - - #[fail(display = "multiple embeddings with the in-template name {:?} where found", name)] - DuplicateEmbeddingName { name: String }, - - #[fail(display = "template dir has to contain at last one sub-template. dir: {}", dir)] - NoSubTemplatesFound { dir: DisplayPath }, - - #[fail(display = "sub-template folder does not contain a template file: {}", dir)] - TemplateFileMissing { dir: DisplayPath }, - - #[fail(display = "I/O Error occurred when loading the spec")] - IoError, - - #[fail(display = "the template/embedding/attachment <{}> is not a file", _0)] - NotAFile(DisplayPath), - - #[fail(display = "given file does not have a valid (i.e. us-ascii/utf8) file stem: {}", _0)] - NoValidFileStem { file: DisplayPath }, - - #[fail(display = "no media type is registered for the file stem {:?}", stem)] - NoMediaTypeFor { stem: String }, - - #[fail(display = "stem and content type differ: {:?} != {:?} wrt. {}", - by_extension, by_content, path)] - FileStemAndContentDifferInMediaType { - path: DisplayPath, - by_extension: String, - by_content: String - }, - - #[fail(display = "media type is not valid utf-8")] - NonUtf8MediaType, - - #[fail(display = "the media type generated by a media type sniffer is invalid")] - NotAMediaType, - - #[fail(display = "the spec with the id {:?} was overriden through a load_from_* function", id)] - AccidentalSpecOverride { id: String }, - - #[fail(display = "constructing a IRI with the scheme {} and the path {} failed", scheme, tail)] - IRIConstructionFailed { - scheme: &'static str, - tail: DisplayPath - } -} - - -#[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub struct DisplayPath(pub PathBuf); - -impl DisplayPath { - pub fn as_path(&self) -> &Path { - self.0.as_path() - } -} - -impl Display for DisplayPath { - fn fmt(&self, fter: &mut fmt::Formatter) -> fmt::Result { - Display::fmt(&self.0.display(), fter) - } -} - -impl<'a> From<&'a OsStr> for DisplayPath { - fn from(path: &'a OsStr) -> Self { - DisplayPath(Path::new(path).to_owned()) - } -} - -impl<'a> From<&'a Path> for DisplayPath { - fn from(path: &'a Path) -> Self { - DisplayPath(PathBuf::from(path)) - } -} - -impl From<PathBuf> for DisplayPath { - fn from(path: PathBuf) -> Self { - DisplayPath(path) - } -} - -impl From<OsString> for DisplayPath { - fn from(path: OsString) -> Self { - DisplayPath(path.into()) - } -} - -impl From<DisplayPath> for PathBuf { - fn from(dp: DisplayPath) -> Self { - dp.0 - } -} - -impl AsRef<Path> for DisplayPath { - fn as_ref(&self) -> &Path { - self - } -} - -impl Deref for DisplayPath { - type Target = Path; - - fn deref(&self) -> &Self::Target { - self.as_path() - } -} - -impl PartialEq<Path> for DisplayPath { - - fn eq(&self, other: &Path) -> bool { - self.as_path() == other - } -} - -impl<'a> PartialEq<&'a Path> for DisplayPath { - fn eq(&self, other: &&'a Path) -> bool { - self == *other - } -} - -impl PartialEq<PathBuf> for DisplayPath { - - fn eq(&self, other: &PathBuf) -> bool { - self.as_path() == other - } -}
\ No newline at end of file diff --git a/src/render_template_engine/from_dir.rs b/src/render_template_engine/from_dir.rs deleted file mode 100644 index 89906d8..0000000 --- a/src/render_template_engine/from_dir.rs +++ /dev/null @@ -1,141 +0,0 @@ -use std::path::{Path, PathBuf}; -use std::collections::HashMap; - -use vec1::Vec1; - -use mail::context::Source; -use mail::{Resource, IRI}; - -use ::render_template_engine::error::{LoadingSpecError, LoadingSpecErrorVariant}; -use ::render_template_engine::utils::{new_string_path, new_str_path}; -use ::render_template_engine::{TemplateSpec, SubTemplateSpec}; -use ::render_template_engine::settings::{LoadSpecSettings, Type}; - -//TODO missing global template level embeddings -//TODO missing caching (of Resources) - - -pub(crate) fn from_dir(base_path: &Path, settings: &LoadSpecSettings) -> Result<TemplateSpec, LoadingSpecError> { - let mut glob_embeddings = HashMap::new(); - let mut sub_template_dirs = Vec::new(); - for folder in base_path.read_dir()? { - let entry = folder?; - if entry.file_type()?.is_dir() { - let type_name = entry.file_name() - .into_string().map_err(|_| LoadingSpecErrorVariant::NonStringPath(entry.path().into()))?; - let (prio, type_) = settings.get_type_with_priority(&*type_name) - .ok_or_else(|| LoadingSpecErrorVariant::MissingTypeInfo { type_name: type_name.clone() })?; - sub_template_dirs.push((prio, entry.path(), type_)); - } else { - let (name, resource_spec) = embedding_from_path(entry.path(), settings)?; - glob_embeddings.insert(name, resource_spec); - } - } - - sub_template_dirs.sort_by_key(|data| data.0); - - let mut sub_specs = Vec::with_capacity(sub_template_dirs.len()); - for (_, dir_path, type_) in sub_template_dirs { - sub_specs.push(sub_template_from_dir(&*dir_path, type_, settings)?); - } - - let sub_specs = Vec1::from_vec(sub_specs) - .map_err(|_| LoadingSpecErrorVariant::NoSubTemplatesFound { dir: base_path.into() })?; - TemplateSpec::new_with_embeddings_and_base_path( - sub_specs, glob_embeddings, base_path.to_owned()) -} - - -//NOTE: if this is provided as a pub utility provide a wrapper function instead which -// only accepts dir_path + settings and gets the rest from it -fn sub_template_from_dir(dir: &Path, type_: &Type, settings: &LoadSpecSettings) - -> Result<SubTemplateSpec, LoadingSpecError> -{ - let template_file = find_template_file(dir, type_)?; - let media_type = type_.to_media_type_for(&*template_file)?; - let embeddings = find_embeddings(dir, &*template_file, settings)?; - - SubTemplateSpec::new(template_file, media_type, embeddings, Vec::new()) -} - -fn find_template_file(dir: &Path, type_: &Type) -> Result<PathBuf, LoadingSpecError> { - let base_name = type_.template_base_name(); - let file = type_.suffixes() - .iter() - .map(|suffix| dir.join(base_name.to_owned() + suffix)) - .find(|path| path.exists()) - .ok_or_else(|| LoadingSpecErrorVariant::TemplateFileMissing { dir: dir.into() })?; - - Ok(file) -} - - -fn find_embeddings(target_path: &Path, template_file: &Path, settings: &LoadSpecSettings) - -> Result<HashMap<String, Resource>, LoadingSpecError> -{ - use std::collections::hash_map::Entry::*; - - let mut embeddings = HashMap::new(); - for entry in target_path.read_dir()? { - let entry = entry?; - let path = entry.path(); - if path != template_file { - let (key, value) = embedding_from_path(path, settings)?; - match embeddings.entry(key) { - Occupied(oe) => { - return Err(LoadingSpecErrorVariant::DuplicateEmbeddingName { name: oe.key().clone() }.into()); - }, - Vacant(ve) => {ve.insert(value);} - } - } - } - Ok(embeddings) -} - -fn embedding_from_path(path: PathBuf, settings: &LoadSpecSettings) - -> Result<(String, Resource), LoadingSpecError> -{ - if !path.is_file() { - return Err(LoadingSpecErrorVariant::NotAFile(path.into()).into()); - } - - let file_name = new_string_path( - path.file_name() - // UNWRAP_SAFE: file_name returns the file (,dir,symlink) name which - // has to exist for a dir_entry - .unwrap())?; - - let name = file_name.split(".") - .next() - //UNWRAP_SAFE: Split iterator has always at last one element - .unwrap() - .to_owned(); - - //TODO we can remove the media type sniffing from here - let media_type = settings.determine_media_type(&path)?; - - let source = Source { - iri: iri_from_path(path)?, - use_name: None, - use_media_type: Some(media_type) - }; - - let resource = Resource::new(source); - - Ok((name, resource)) -} - -fn iri_from_path<IP: AsRef<Path> + Into<PathBuf>>(path: IP) -> Result<IRI, LoadingSpecError> { - { - let path_ref = path.as_ref(); - if let Ok(strfy) = new_str_path(&path_ref) { - if let Ok(iri) = IRI::from_parts("path", strfy) { - return Ok(iri) - } - } - } - Err(LoadingSpecErrorVariant::IRIConstructionFailed { - scheme: "path", - tail: path.into().into() - }.into()) -}
\ No newline at end of file diff --git a/src/render_template_engine/mod.rs b/src/render_template_engine/mod.rs deleted file mode 100644 index 6b16035..0000000 --- a/src/render_template_engine/mod.rs +++ /dev/null @@ -1,389 +0,0 @@ -use std::path::{Path, PathBuf}; -use std::collections::HashMap; -use std::mem::replace; - -use failure::Fail; -use serde::{Serialize, Serializer}; -use vec1::Vec1; - -use mail::{Resource, Context}; -use mail::file_buffer::FileBuffer; -use headers::components::MediaType; - -use ::TemplateEngine; -use ::template::{BodyPart, MailParts}; -use ::resource::{Attachment, EmbeddingWithCId}; - - -use self::error::{Error, LoadingSpecError, LoadingSpecErrorVariant}; -use self::utils::{new_string_path, string_path_set, check_string_path, fix_newlines}; - -pub mod error; -mod utils; -mod settings; -pub use self::settings::*; -mod from_dir; - -pub trait RenderEngine { - const PRODUCES_VALID_NEWLINES: bool; - type Error: Fail; - //any caching is done inside transparently - fn render<D: Serialize>(&self, id: &str, data: &D) -> Result<String, Self::Error>; - -} - -#[derive(Debug)] -pub struct RenderTemplateEngine<R: RenderEngine> { - fix_newlines: bool, - render_engine: R, - id2spec: HashMap<String, TemplateSpec>, -} - -impl<R> RenderTemplateEngine<R> - where R: RenderEngine -{ - pub fn new(render_engine: R) -> Self { - RenderTemplateEngine { - render_engine, - id2spec: Default::default(), - fix_newlines: !R::PRODUCES_VALID_NEWLINES, - } - } - - pub fn set_fix_newlines(&mut self, should_fix_newlines: bool) { - self.fix_newlines = should_fix_newlines - } - - pub fn does_fix_newlines(&self) -> bool { - self.fix_newlines - } - - pub fn add_spec(&mut self, id: String, spec: TemplateSpec) -> Option<TemplateSpec> { - self.id2spec.insert(id, spec) - } - - pub fn remove_spec(&mut self, id: &str) -> Option<TemplateSpec> { - self.id2spec.remove(id) - } - - pub fn specs(&self) -> &HashMap<String, TemplateSpec> { - &self.id2spec - } - -// pub fn specs_mut(&mut self) -> &mut HashMap<String, TemplateSpec> { -// &mut self.specs() -// } - - pub fn lookup_spec(&self, template_id: &str) -> Result<&TemplateSpec, Error<R::Error>> { - self.id2spec - .get(template_id) - .ok_or_else(|| Error::from_unknown_template_id(template_id)) - } - - pub fn load_specs_from_dir<P>( - &mut self, - dir_path: P, - settings: &LoadSpecSettings - ) -> Result<(), LoadingSpecError> - where P: AsRef<Path> - { - self._load_specs_from_dir(dir_path.as_ref(), settings, false) - } - - pub fn load_specs_from_dir_allow_override<P>( - &mut self, - dir_path: P, - settings: &LoadSpecSettings - ) -> Result<(), LoadingSpecError> - where P: AsRef<Path> - { - self._load_specs_from_dir(dir_path.as_ref(), settings, true) - } - - fn _load_specs_from_dir( - &mut self, - dir_path: &Path, - settings: &LoadSpecSettings, - allow_override: bool - ) -> Result<(), LoadingSpecError> - { - for entry in dir_path.read_dir()? { - let entry = entry?; - if entry.metadata()?.is_dir() { - let id = entry.file_name() - .into_string() - .map_err(|file_name| LoadingSpecErrorVariant::NonStringPath(file_name.into()))?; - let spec = TemplateSpec::from_dir(entry.path(), settings)?; - let old = self.add_spec(id, spec); - if old.is_some() && !allow_override { - // we already know that the file name can be converted into a string - let file_name = entry.file_name().into_string().unwrap(); - return Err(LoadingSpecErrorVariant::AccidentalSpecOverride { id: file_name }.into()); - } - } - } - Ok(()) - } - -} - -impl<R, C> TemplateEngine<C> for RenderTemplateEngine<R> - where R: RenderEngine, C: Context -{ - type TemplateId = str; - type Error = Error<R::Error>; - - fn use_templates<D: Serialize>( - &self, - ctx: &C, - template_id: &str, - data: &D - ) -> Result<MailParts, Self::Error > - { - let spec = self.lookup_spec(template_id)?; - - //OPTIMIZE there should be a more efficient way - // maybe use Rc<str> as keys? and Rc<Resource> for embeddings? - let shared_embeddings = spec.embeddings().iter() - .map(|(key, resource_spec)| - create_embedding(key.to_owned(),resource_spec.clone(), ctx)) - .collect::<HashMap<_,_>>(); - - let mut attachments = Vec::new(); - let bodies = spec.sub_specs().try_mapped_ref(|template| { - - let embeddings = template.embeddings.iter() - .map(|(key, resource_spec)| - create_embedding(key.to_owned(),resource_spec.clone(), ctx)) - .collect::<HashMap<_,_>>(); - - let rendered = { - // make CIds available to render engine - let data = DataWrapper { data, cids: (&embeddings, &shared_embeddings) }; - let path = template.str_path(); - self.render_engine.render(&*path, &data) - .map_err(|re| Error::RenderError(re))? - }; - - let rendered = - if self.fix_newlines { - fix_newlines(rendered) - } else { - rendered - }; - - let buffer = FileBuffer::new(template.media_type().clone(), rendered.into()); - let resource = Resource::sourceless_from_buffer(buffer); - - attachments.extend(template.attachments().iter() - .map(|resource| Attachment::new(resource.clone()))); - - Ok(BodyPart { - body_resource: resource, - embeddings: embeddings.into_iter().map(|(_,v)| v).collect() - }) - })?; - - Ok(MailParts { - alternative_bodies: bodies, - shared_embeddings: shared_embeddings.into_iter().map(|(_, v)| v).collect(), - attachments, - }) - } -} - -fn create_embedding<C>(key: String, resource: Resource, ctx: &C) -> (String, EmbeddingWithCId) - where C: Context -{ - let cid = ctx.generate_content_id(); - (key, EmbeddingWithCId::new(resource, cid)) -} - - -#[derive(Debug)] -pub struct TemplateSpec { - /// the `base_path` which was used to construct the template from, - /// e.g. with `TemplateSpec::from_dir` and which is used for reloading - base_path: Option<PathBuf>, - templates: Vec1<SubTemplateSpec>, - /// template level embeddings, i.e. embeddings shared between alternative bodies - embeddings: HashMap<String, Resource> -} - -impl TemplateSpec { - - /// - /// ```no_rust - /// templates/ - /// templateA/ - /// html/ - /// mail.html - /// emb_logo.png - /// text/ - /// mail.text - /// ``` - /// - /// Note: the file name "this.is.a" is interprete as name "this" with suffix/type ".is.a" - /// so it's cid gan be accessed with "cids.this" - #[inline] - pub fn from_dir<P>(base_path: P, settings: &LoadSpecSettings) -> Result<TemplateSpec, LoadingSpecError> - where P: AsRef<Path> - { - from_dir::from_dir(base_path.as_ref(), settings) - } - - pub fn new(templates: Vec1<SubTemplateSpec>) -> Self { - Self::new_with_embeddings(templates, Default::default()) - } - - pub fn new_with_embeddings( - templates: Vec1<SubTemplateSpec>, - embeddings: HashMap<String, Resource> - ) -> Self { - TemplateSpec { base_path: None, templates, embeddings } - } - - pub fn new_with_base_path<P>(templates: Vec1<SubTemplateSpec>, base_path: P) - -> Result<Self, LoadingSpecError> - where P: AsRef<Path> - { - Self::new_with_embeddings_and_base_path( - templates, Default::default(), base_path.as_ref() - ) - } - - pub fn new_with_embeddings_and_base_path<P>( - templates: Vec1<SubTemplateSpec>, - embeddings: HashMap<String, Resource>, - base_path: P - ) -> Result<Self, LoadingSpecError> - where P: AsRef<Path> - { - let path = base_path.as_ref().to_owned(); - check_string_path(&*path)?; - Ok(TemplateSpec { base_path: Some(path), templates, embeddings }) - } - - pub fn sub_specs(&self) -> &Vec1<SubTemplateSpec> { - &self.templates - } - - pub fn sub_specs_mut(&mut self) -> &mut Vec1<SubTemplateSpec> { - &mut self.templates - } - - pub fn embeddings(&self) -> &HashMap<String, Resource> { - &self.embeddings - } - - pub fn embeddings_mut(&mut self) -> &mut HashMap<String, Resource> { - &mut self.embeddings - } - - - pub fn base_path(&self) -> Option<&Path> { - self.base_path.as_ref().map(|r| &**r) - } - - pub fn set_base_path<P>(&mut self, new_path: P) -> Result<Option<PathBuf>, LoadingSpecError> - where P: AsRef<Path> - { - let path = new_path.as_ref(); - check_string_path(path)?; - Ok(replace(&mut self.base_path, Some(path.to_owned()))) - } - -} - -#[derive(Debug)] -pub struct SubTemplateSpec { - media_type: MediaType, - /// The path to the template file if it is a relative path it is - /// used relative to the working directory - path: String, - // (Name, Resource) | name is used by the template engine e.g. log, and differs to - // resource spec use_name which would - // e.g. be logo.png but referring to the file long_logo_name.png - embeddings: HashMap<String, Resource>,//todo use ordered map - attachments: Vec<Resource> -} - -impl SubTemplateSpec { - - //FIXME to many arguments alternatives: builder, - // default values (embedding, attachment)+then setter, - // default values + then with_... methods - pub fn new<P>(path: P, - media_type: MediaType, - embeddings: HashMap<String, Resource>, - attachments: Vec<Resource> - ) -> Result<Self, LoadingSpecError> - where P: AsRef<Path> - { - let path = new_string_path(path.as_ref())?; - Ok(SubTemplateSpec { path, media_type, embeddings, attachments }) - } - - pub fn path(&self) -> &Path { - Path::new(&self.path) - } - - pub fn str_path(&self) -> &str { - &self.path - } - - pub fn set_path<P>(&mut self, new_path: P) -> Result<PathBuf, LoadingSpecError> - where P: AsRef<Path> - { - string_path_set(&mut self.path, new_path.as_ref()) - } - - pub fn media_type(&self) -> &MediaType { - &self.media_type - } - - pub fn set_media_type(&mut self, media_type: MediaType) -> MediaType { - //we might wan't to add restrictions at some point,e.g. no multipart |