Created
January 19, 2020 20:04
-
-
Save kespindler/ca949af18d429590bd4cc1f6342eb2a4 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[package] | |
name = "qrgen" | |
version = "0.1.0" | |
authors = ["kurt spindler <[email protected]>"] | |
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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 ¶ms.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(¶ms) { | |
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:[email protected] | |
// 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