Skip to content

Instantly share code, notes, and snippets.

@kespindler
Created January 19, 2020 20:04
Show Gist options
  • Save kespindler/ca949af18d429590bd4cc1f6342eb2a4 to your computer and use it in GitHub Desktop.
Save kespindler/ca949af18d429590bd4cc1f6342eb2a4 to your computer and use it in GitHub Desktop.
[package]
name = "qrgen"
version = "0.1.0"
authors = ["kurt spindler <kespindler@gmail.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
tide = "0.5.1"
async-std = "1.4.0"
qrcode = "0.11.2"
vcard = "0.4.3"
failure = "0.1.6"
mime = "0.3.16"
serde = { version = "1.0", features = ["derive"] }
image = "0.22.4"
futures-io = "0.3.1"
tokio = { version = "0.2", features = ["full"] }
http-service = "0.4.0"
itertools = "0.8.2"
http = "0.2.0"
[profile.release]
# used this to do some profiling
debug = true
use tide;
use async_std::task;
use qrcode::QrCode;
use vcard;
use vcard::properties::{Begin, Version, FormattedName, Name, End, Telephone};
use vcard::values::{version_value::VersionValue, name_value::NameValue, text::Component, text::Text};
use std::collections::HashSet;
use failure::{Error, err_msg};
use serde::Deserialize;
use image::{PNG, JPEG, Luma, ImageLuma8, GrayImage};
use std::io::Cursor;
use http_service;
use itertools::Itertools;
use vcard::values::type_value::TypeValueWithTelephoneType;
use vcard::values::telephone_value::TelephoneValue;
use vcard::parameters::typ::TypeWithTelType;
type Request = tide::Request<()>;
type Response = tide::Response;
type Phone = String;
struct Contact {
first: Option<String>,
last: Option<String>,
phone: Option<Phone>,
}
enum QRFormat {
PNG,
JPG,
}
struct QROptions {
format: QRFormat
}
#[derive(Deserialize)]
struct ContactParams {
format: Option<String>,
first: Option<String>,
last: Option<String>,
phone: Option<String>,
}
impl From<ContactParams> for Contact {
fn from(s: ContactParams) -> Contact {
Contact {
first: s.first,
last: s.last,
phone: s.phone,
}
}
}
fn create_contact_params(params: &ContactParams) -> Result<QROptions, Error> {
let format = match &params.format {
None => QRFormat::PNG,
Some(s) => match s.as_str() {
"png" => QRFormat::PNG,
"jpg" => QRFormat::JPG,
_ => return Err(err_msg("Invalid format parameter."))
}
};
Ok(QROptions {
format,
})
}
impl From<Contact> for vcard::VCard {
fn from(contact: Contact) -> Self {
let version = Version {
any: None,
value: VersionValue::V4P0,
};
let formatted_names = {
let mut s = HashSet::new();
let fullname: String = vec![
contact.first.clone(),
contact.last.clone()
].into_iter().filter_map(|e| e).join(" ");
let text: Text = Text::from_str(fullname.as_str()).unwrap();
let formatted_name = FormattedName::from_text(text);
s.insert(formatted_name);
vcard::Set::from_hash_set(s).unwrap()
};
let names = {
let mut s = HashSet::new();
let last = match contact.last {
None => None,
Some(s) => Component::from_str(s.as_str()).ok()
};
let first = match contact.first {
None => None,
Some(s) => Component::from_str(s.as_str()).ok()
};
s.insert(Name::from_name_value(
NameValue::from_components(
last, first, None, None, None,
)
));
vcard::Set::from_hash_set(s).ok()
};
let telephones = match contact.phone {
None => None,
Some(_) => {
let mut telephones = HashSet::new();
let home_phone = {
let mut telephone = Telephone::from_telephone_value(TelephoneValue::from_telephone_number_str(
contact.phone.unwrap(),
None::<&str>,
).unwrap());
let type_values = {
let mut type_values = HashSet::new();
type_values.insert(TypeValueWithTelephoneType::Home);
type_values.insert(TypeValueWithTelephoneType::Voice);
vcard::Set::from_hash_set(type_values).unwrap()
};
if let Telephone::TelephoneValue { ref mut typ, .. } = telephone {
*typ = Some(TypeWithTelType::from_type_values(type_values));
}
telephone
};
telephones.insert(home_phone);
Some(vcard::Set::from_hash_set(telephones).unwrap())
}
};
vcard::VCard {
begin: Begin,
version,
formatted_names,
names,
nicknames: None,
uid: None,
keys: None,
gender: None,
birthdays: None,
anniversaries: None,
addresses: None,
telephones: telephones,
emails: None,
titles: None,
roles: None,
photos: None,
logos: None,
urls: None,
sounds: None,
organizations: None,
members: None,
relationships: None,
categories: None,
notes: None,
languages: None,
time_zones: None,
geos: None,
impps: None,
sources: None,
product_id: None,
client_property_id_maps: None,
fburls: None,
calendar_uris: None,
calendar_address_uris: None,
x_properties: None,
revision: None,
end: End,
}
}
}
fn make_qr_code(contact: Contact) -> Result<QrCode, Error> {
let vc = vcard::VCard::from(contact);
let bytes = vc.to_string().into_bytes();
println!("{}", vc);
match QrCode::new(bytes) {
Ok(code) => Ok(code),
Err(e) => Err(err_msg(format!("{}", e)))
}
}
async fn generate_contact_card_view(req: Request) -> Response {
// TODO whats the error displayed when ? fails?
let params = match req.query::<ContactParams>() {
Ok(s) => s,
Err(_) => return Response::new(500).body_string(
"Could not parse query params.".to_string()
)
};
let options = match create_contact_params(&params) {
Ok(s) => s,
Err(_) => return Response::new(500).body_string(
"Could not parse query params.".to_string()
)
};
let contact = Contact::from(params);
let qr = match make_qr_code(contact) {
Ok(s) => s,
Err(_) => return Response::new(500).body_string(
"Encountered an error rendering the QR code.".to_string())
};
// Can test with a static QR code like so.
// let qr = QrCode::new("BEGIN:VCARD
// VERSION:4.0
// N:Gump;Forrest;;Mr.;
// FN:Forrest Gump
// ORG:Bubba Gump Shrimp Co.
// TITLE:Shrimp Man
// PHOTO;MEDIATYPE=image/gif:http://www.example.com/dir_photos/my_photo.gif
// TEL;HOME;VOICE:+14041231234
// TEL;TYPE=home,voice;VALUE=uri:tel:+14045551212
// ADR;TYPE=WORK;PREF=1;LABEL=\"100 Waters Edge\\nBaytown\\, LA 30314\\nUnited States of America\":;;100 Waters Edge;Baytown;LA;30314;United States of America
// ADR;TYPE=HOME;LABEL=\"42 Plantation St.\\nBaytown\\, LA 30314\\nUnited States of America\":;;42 Plantation St.;Baytown;LA;30314;United States of America
// EMAIL:forrestgump@example.com
// REV:20080424T195243Z
// x-qq:21588891
// END:VCARD".as_bytes()).unwrap();
let image: GrayImage = qr.render::<Luma<u8>>().build();
let ref mut buf = Cursor::new(Vec::new());
let res = Response::new(200);
let mut res2: http_service::Response = res.into();
match options.format {
QRFormat::PNG => {
ImageLuma8(image).write_to(buf, PNG);
res2.headers_mut().insert(
"Content-Type",
format!("{}", mime::IMAGE_PNG).parse().unwrap(),
);
}
QRFormat::JPG => {
ImageLuma8(image).write_to(buf, JPEG);
res2.headers_mut().insert(
"Content-Type",
format!("{}", mime::IMAGE_JPEG).parse().unwrap(),
);
}
}
buf.set_position(0);
let vec = buf.clone().into_inner();
*res2.body_mut() = vec.into();
let res3 = Response::from(res2);
res3
}
fn main() -> Result<(), std::io::Error> {
task::block_on(async {
let mut app = tide::new();
app.at("/").get(|_| async move { "POST to /contact to create a QR code." });
app.at("/contact").post(generate_contact_card_view);
app.listen("127.0.0.1:8080").await?;
Ok(())
})
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment