Skip to content

Commit

Permalink
TemplateString struct (#240)
Browse files Browse the repository at this point in the history
* Create the `TemplateString` struct.

This struct can be used in `AybConfig` to define a template string, it is both serialized and deserialized as a string. This _wrapper_ struct provides the `execute` function, which provided a Vec of string tuples replaces the key (left value) with the value (right value).

* Test the `TemplateString` struct

* Review comment
  • Loading branch information
sofiaritz authored Dec 17, 2023
1 parent 6a98cc0 commit 18893c2
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 9 deletions.
22 changes: 14 additions & 8 deletions src/email/templating.rs
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
use crate::http::structs::AybConfigEmail;

const CLI_CONFIRM_TMPL: &str = "To complete your registration, type\n\tayb client confirm {token}";
const WEB_CONFIRM_TMPL: &str = "To complete your registration, visit\n\t {url}";
use crate::templating::TemplateString;

pub fn render_confirmation_template(config: &AybConfigEmail, token: &str) -> String {
let cli_confirm_tmpl: TemplateString =
"To complete your registration, type\n\tayb client confirm {token}"
.to_string()
.into();
let web_confirm_tmpl: TemplateString = "To complete your registration, visit\n\t {url}"
.to_string()
.into();

if let Some(tmpl_conf) = &config.templates {
if let Some(confirm_conf) = &tmpl_conf.confirm {
return WEB_CONFIRM_TMPL.replace(
"{url}",
return web_confirm_tmpl.execute(vec![(
"url",
&confirm_conf
.confirmation_url
.replace("{token}", &urlencoding::encode(token)),
);
.execute(vec![("token", &urlencoding::encode(token))]),
)]);
}
}

CLI_CONFIRM_TMPL.replace("{token}", token)
cli_confirm_tmpl.execute(vec![("token", token)])
}
3 changes: 2 additions & 1 deletion src/http/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use crate::ayb_db::models::{
DBType, EntityType, InstantiatedDatabase as PersistedDatabase, InstantiatedDatabase,
InstantiatedEntity as PersistedEntity,
};
use crate::templating::TemplateString;
use prettytable::{format, Cell, Row, Table};
use serde::{Deserialize, Serialize};

Expand All @@ -18,7 +19,7 @@ pub struct AybConfigAuthentication {

#[derive(Clone, Serialize, Deserialize)]
pub struct AybConfigEmailTemplatesConfirm {
pub confirmation_url: String,
pub confirmation_url: TemplateString,
}

#[derive(Clone, Serialize, Deserialize)]
Expand Down
1 change: 1 addition & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ pub mod email;
pub mod error;
pub mod hosted_db;
pub mod http;
pub mod templating;
98 changes: 98 additions & 0 deletions src/templating.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::Formatter;

#[derive(Clone)]
pub struct TemplateString {
string: String,
}

impl TemplateString {
pub fn execute(&self, values: Vec<(&str, &str)>) -> String {
let mut string = self.string.clone();
values
.iter()
.for_each(|(k, v)| string = string.replace(&format!("{{{}}}", k), v));
string
}
}

impl Serialize for TemplateString {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.string)
}
}

struct TmplStrVisitor;

impl<'de> Visitor<'de> for TmplStrVisitor {
type Value = TemplateString;

fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result {
formatter.write_str("a valid template string")
}

fn visit_string<E>(self, v: String) -> Result<Self::Value, E>
where
E: Error,
{
Ok(TemplateString { string: v })
}

fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: Error,
{
Ok(TemplateString { string: v.into() })
}
}

impl<'de> Deserialize<'de> for TemplateString {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(TmplStrVisitor)
}
}

impl From<String> for TemplateString {
fn from(value: String) -> Self {
Self { string: value }
}
}

#[cfg(test)]
mod tests {
use crate::templating::TemplateString;

#[test]
fn deserializes_properly() {
let str = TemplateString {
string: "Hi {name}!".into(),
};

assert_eq!(
serde_json::to_string(&str).unwrap(),
"\"Hi {name}!\"".to_string()
);
}

#[test]
fn serializes_properly() {
let str: TemplateString = serde_json::from_str("\"Hi {name}!\"").unwrap();

assert_eq!(str.string, "Hi {name}!".to_string());
}

#[test]
fn executes_properly() {
let str = TemplateString::from("Hello, {name1} and {name2}!".to_string());
let result = str.execute(vec![("name1", "Alice"), ("name2", "Bob")]);

assert_eq!(result, "Hello, Alice and Bob!".to_string())
}
}

0 comments on commit 18893c2

Please sign in to comment.