use chrono::{prelude::*, Duration};
use fastly::{Error, Request, Response};
use serde::Serialize;
use tera::{Context, Tera};
#[derive(Serialize)]
struct InvoiceItem {
name: String,
#[serde(serialize_with = "serialize_price")]
price: f32,
}
fn serialize_price<S: serde::Serializer>(price: &f32, s: S) -> Result<S::Ok, S::Error> {
let price_str = format!("{:.2}", price);
s.serialize_str(&price_str)
}
#[fastly::main]
fn main(_req: Request) -> Result<Response, Error> {
let invoice_items = vec![
InvoiceItem {
name: "Pet Food".into(),
price: 19.99,
},
InvoiceItem {
name: "Leash".into(),
price: 10.75,
},
InvoiceItem {
name: "Shampoo".into(),
price: 9.99,
},
];
let total_price: f32 = invoice_items.iter().map(|item| item.price).sum();
let now = Local::now();
let due = now + Duration::days(30);
let date_created = now.format("%b %e %Y").to_string();
let date_due = due.format("%b %e %Y").to_string();
let mut context = Context::new();
context.insert("page_title", "Sample Invoice, powered by Fastly");
context.insert("vendor_title", "Fastly Invoice");
context.insert("address", "P.O. Box 78266<br/>San Francisco, CA 94107 ");
context.insert("customer", "Example Corp.<br />billing@example.com");
context.insert("payment", "Credit card");
context.insert("date_created", &date_created);
context.insert("date_due", &date_due);
context.insert("invoice_items", &invoice_items);
context.insert("total_price", &format!("{:.2}", total_price));
context.insert("invoice_num", "10937248");
context.insert("css_style", INVOICE_STYLE);
let invoice = Tera::default()
.render_str(INVOICE_TEMPLATE, &context)
.unwrap();
Ok(Response::from_body(invoice))
}
const INVOICE_TEMPLATE: &str = r#"
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>{{page_title}}</title>
<style>{{css_style}}</style>
</head>
<body>
<div class="invoice-box">
<table cellpadding="0" cellspacing="0">
<tr class="top">
<td colspan="2">
<table>
<tr>
<td class="title">{{vendor_title}}</td>
<td>
Invoice #: {{invoice_num}}<br />
Created: {{date_created}}<br />
Due: {{date_due}}
</td>
</tr>
</table>
</td>
</tr>
<tr class="information">
<td colspan="2">
<table>
<tr>
<td>{{address}}</td>
<td>{{customer}}</td>
</tr>
</table>
</td>
</tr>
<tr class="heading"><td>Item</td><td>Price</td></tr>
{% for item in invoice_items %}
<tr class="item">
<td>{{item.name}}</td>
<td>${{item.price}}</td>
</tr>
{% endfor %}
<tr class="total">
<td></td>
<td>Total: ${{total_price}}</td>
</tr>
</table>
</div>
</body>
</html>
"#;
const INVOICE_STYLE: &str = r#"
.invoice-box {
max-width: 800px;
margin: auto;
padding: 30px;
border: 1px solid #eee;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
font-size: 16px;
line-height: 24px;
font-family: 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;
color: #555;
}
.invoice-box table {
width: 100%;
line-height: inherit;
text-align: left;
}
.invoice-box table td {
padding: 5px;
vertical-align: top;
}
.invoice-box table tr td:nth-child(2) {
text-align: right;
}
.invoice-box table tr.top table td {
padding-bottom: 20px;
}
.invoice-box table tr.top table td.title {
font-size: 45px;
line-height: 45px;
color: #333;
}
.invoice-box table tr.information table td {
padding-bottom: 40px;
}
.invoice-box table tr.heading td {
background: #eee;
border-bottom: 1px solid #ddd;
font-weight: bold;
}
.invoice-box table tr.details td {
padding-bottom: 20px;
}
.invoice-box table tr.item td {
border-bottom: 1px solid #eee;
}
.invoice-box table tr.item.last td {
border-bottom: none;
}
.invoice-box table tr.total td:nth-child(2) {
border-top: 2px solid #eee;
font-weight: bold;
}
@media only screen and (max-width: 600px) {
.invoice-box table tr.top table td {
width: 100%;
display: block;
text-align: center;
}
.invoice-box table tr.information table td {
width: 100%;
display: block;
text-align: center;
}
}
/** RTL **/
.invoice-box.rtl {
direction: rtl;
font-family: Tahoma, 'Helvetica Neue', 'Helvetica', Helvetica, Arial, sans-serif;
}
.invoice-box.rtl table {
text-align: right;
}
.invoice-box.rtl table tr td:nth-child(2) {
text-align: left;
}
"#;