use serde_derive::{Deserialize, Serialize};
use std::collections::HashMap;
use field::Field;
use util::*;
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Pass {
pub description: String,
pub format_version: i32,
pub organization_name: String,
pub pass_type_identifier: String,
pub serial_number: String,
pub team_identifier: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(rename = "appLaunchURL")]
#[serde(default)]
pub app_launch_url: Option<String>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub associated_store_identifiers: Vec<i32>,
#[serde(default)]
pub user_info: HashMap<String, String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub expiration_date: Option<String>,
#[serde(skip_serializing_if = "is_false")]
#[serde(default)]
pub voided: bool,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub beacons: Vec<Beacon>,
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub locations: Vec<Location>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub max_distance: Option<u32>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub relevant_date: Option<String>,
#[serde(flatten)]
pub style: Style,
#[serde(default)]
#[serde(flatten)]
pub visual: Option<VisualAppearance>,
#[serde(flatten)]
#[serde(default)]
pub web_service: Option<WebService>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub nfc: Option<NFC>,
}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
#[serde(rename_all = "camelCase")]
#[serde(default)]
pub struct VisualAppearance {
#[serde(skip_serializing_if = "Vec::is_empty")]
#[serde(default)]
pub barcodes: Vec<Barcode>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub background_color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub foreground_color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub grouping_identifier: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub label_color: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub logo_text: Option<String>,
#[serde(skip_serializing_if = "is_false")]
#[serde(default)]
pub suppress_strip_shine: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
#[serde(default)]
pub struct Beacon {
#[serde(rename = "proximityUUID")]
pub proximity_uuid: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub major: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub minor: Option<u16>,
#[serde(skip_serializing_if = "Option::is_none")]
pub relevant_text: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone, Default)]
#[serde(rename_all = "camelCase")]
#[serde(default)]
pub struct Location {
#[serde(skip_serializing_if = "Option::is_none")]
pub altitude: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub latitude: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub longitude: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub relevant_text: Option<String>,
}
#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub enum Style {
BoardingPass(Structure),
Coupon(Structure),
EventTicket(Structure),
Generic(Structure),
StoreCard(Structure),
}
impl Default for Style {
fn default() -> Style {
Style::Generic(Default::default())
}
}
#[derive(Debug, Serialize, Deserialize, Default, Clone)]
#[serde(rename_all = "camelCase")]
#[serde(default)]
pub struct Structure {
#[serde(skip_serializing_if = "Vec::is_empty")]
auxiliary_fields: Vec<Field>,
#[serde(skip_serializing_if = "Vec::is_empty")]
back_fields: Vec<Field>,
#[serde(skip_serializing_if = "Vec::is_empty")]
header_fields: Vec<Field>,
#[serde(skip_serializing_if = "Vec::is_empty")]
primary_fields: Vec<Field>,
#[serde(skip_serializing_if = "Vec::is_empty")]
secondary_fields: Vec<Field>,
#[serde(skip_serializing_if = "Option::is_none")]
transit_type: Option<TransitType>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum TransitType {
#[serde(rename = "PKTransitTypeAir")]
Air,
#[serde(rename = "PKTransitTypeBoat")]
Boat,
#[serde(rename = "PKTransitTypeBus")]
Bus,
#[serde(rename = "PKTransitTypeGeneric")]
Generic,
#[serde(rename = "PKTransitTypeTrain")]
Train,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct Barcode {
pub message: String,
pub format: BarcodeFormat,
pub message_encoding: String,
#[serde(skip_serializing_if = "Option::is_none")]
#[serde(default)]
pub alt_text: Option<String>,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
pub enum BarcodeFormat {
#[serde(rename = "PKBarcodeFormatQR")]
QR,
#[serde(rename = "PKBarcodeFormatPDF417")]
PDF417,
#[serde(rename = "PKBarcodeFormatAztec")]
Aztec,
#[serde(rename = "PKBarcodeFormatCode128")]
Code128,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct WebService {
pub authentication_token: String,
#[serde(rename = "webServiceURL")]
pub web_service_url: String,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
#[serde(rename_all = "camelCase")]
pub struct NFC {
pub message: String,
#[serde(default)]
pub encryption_public_key: Option<String>,
}
#[derive(Default, Clone)]
pub struct PassBuilder {
serial_number: String,
pass_type_identifier: String,
team_identifier: String,
organization_name: Option<String>,
description: Option<String>,
structure: Structure,
app_launch_url: Option<String>,
associated_store_identifiers: Vec<i32>,
user_info: HashMap<String, String>,
expiration_date: Option<String>,
voided: bool,
beacons: Vec<Beacon>,
locations: Vec<Location>,
max_distance: Option<u32>,
relevant_date: Option<String>,
visual: VisualAppearance,
web_service: Option<WebService>,
nfc: Option<NFC>,
}
impl PassBuilder {
pub fn new<S, I, T>(
serial_number: S,
pass_type_identifier: I,
team_identifier: T,
) -> PassBuilder
where
S: Into<String>,
I: Into<String>,
T: Into<String>,
{
PassBuilder {
serial_number: serial_number.into(),
pass_type_identifier: pass_type_identifier.into(),
team_identifier: team_identifier.into(),
..Default::default()
}
}
pub fn organization_name<O: Into<String>>(mut self, organization_name: O) -> PassBuilder {
self.organization_name = Some(organization_name.into());
self
}
pub fn description<D: Into<String>>(mut self, description: D) -> PassBuilder {
self.description = Some(description.into());
self
}
pub fn app_launch_url<U: Into<String>>(mut self, url: U) -> PassBuilder {
self.app_launch_url = Some(url.into());
self
}
pub fn add_associated_store_identifier(mut self, id: i32) -> PassBuilder {
self.associated_store_identifiers.push(id);
self
}
pub fn add_user_info<K, V>(mut self, key: K, value: V) -> PassBuilder
where
K: Into<String>,
V: Into<String>,
{
self.user_info.insert(key.into(), value.into());
self
}
pub fn expiration_date<D: Into<String>>(mut self, date: D) -> PassBuilder {
self.expiration_date = Some(date.into());
self
}
pub fn voided(mut self) -> PassBuilder {
self.voided = true;
self
}
pub fn add_beacon(mut self, beacon: Beacon) -> PassBuilder {
self.beacons.push(beacon);
self
}
pub fn add_location(mut self, location: Location) -> PassBuilder {
self.locations.push(location);
self
}
pub fn max_distance(mut self, distance: u32) -> PassBuilder {
self.max_distance = Some(distance);
self
}
pub fn relevant_date(mut self, date: String) -> PassBuilder {
self.relevant_date = Some(date);
self
}
pub fn add_auxiliary_field(mut self, field: Field) -> PassBuilder {
self.structure.auxiliary_fields.push(field);
self
}
pub fn add_back_field(mut self, field: Field) -> PassBuilder {
self.structure.back_fields.push(field);
self
}
pub fn add_header_field(mut self, field: Field) -> PassBuilder {
self.structure.header_fields.push(field);
self
}
pub fn add_primary_field(mut self, field: Field) -> PassBuilder {
self.structure.primary_fields.push(field);
self
}
pub fn add_secondary_field(mut self, field: Field) -> PassBuilder {
self.structure.auxiliary_fields.push(field);
self
}
pub fn add_barcode(mut self, barcode: Barcode) -> PassBuilder {
self.visual.barcodes.push(barcode);
self
}
pub fn background_color<C: Into<String>>(mut self, color: C) -> PassBuilder {
self.visual.background_color = Some(color.into());
self
}
pub fn foreground_color<C: Into<String>>(mut self, color: C) -> PassBuilder {
self.visual.foreground_color = Some(color.into());
self
}
pub fn grouping_identifier(mut self, identifier: String) -> PassBuilder {
self.visual.grouping_identifier = Some(identifier);
self
}
pub fn label_color(mut self, color: String) -> PassBuilder {
self.visual.label_color = Some(color);
self
}
pub fn logo_text<T: Into<String>>(mut self, text: T) -> PassBuilder {
self.visual.logo_text = Some(text.into());
self
}
pub fn suppress_strip_shine(mut self) -> PassBuilder {
self.visual.suppress_strip_shine = true;
self
}
pub fn web_service<T, U>(mut self, token: T, url: U) -> PassBuilder
where
T: Into<String>,
U: Into<String>,
{
self.web_service = Some(WebService {
authentication_token: token.into(),
web_service_url: url.into(),
});
self
}
pub fn nfc<M: Into<String>>(mut self, message: M, key: Option<String>) -> PassBuilder {
self.nfc = Some(NFC {
message: message.into(),
encryption_public_key: key,
});
self
}
fn build(self, style: Style) -> Pass {
Pass {
format_version: 1,
serial_number: self.serial_number,
pass_type_identifier: self.pass_type_identifier,
team_identifier: self.team_identifier,
organization_name: self.organization_name.unwrap_or_default(),
description: self.description.unwrap_or_default(),
app_launch_url: self.app_launch_url,
associated_store_identifiers: self.associated_store_identifiers,
user_info: self.user_info,
expiration_date: self.expiration_date,
voided: self.voided,
beacons: self.beacons,
locations: self.locations,
max_distance: self.max_distance,
relevant_date: self.relevant_date,
style,
visual: Some(self.visual),
web_service: self.web_service,
nfc: self.nfc,
}
}
pub fn finish_boarding_pass(self, transit_type: TransitType) -> Pass {
let mut structure = self.structure.clone();
structure.transit_type = Some(transit_type);
self.build(Style::BoardingPass(structure))
}
pub fn finish_coupon(self) -> Pass {
let structure = self.structure.clone();
self.build(Style::Coupon(structure))
}
pub fn finish_event_ticket(self) -> Pass {
let structure = self.structure.clone();
self.build(Style::EventTicket(structure))
}
pub fn finish_generic(self) -> Pass {
let structure = self.structure.clone();
self.build(Style::Generic(structure))
}
pub fn finish_store_card(self) -> Pass {
let structure = self.structure.clone();
self.build(Style::StoreCard(structure))
}
}
mod test {
#[test]
fn ser_pass_example() {
use super::*;
let pass = PassBuilder::new("001", "pass.com.example", "CDHE9L6U22")
.web_service(
"vxwxd7J8AlNNFPS8k0a0FfUFtq0ewzFdc",
"https://example.com/passes/",
).relevant_date("2012-07-22T14:25-08:00".into())
.add_location(Location {
longitude: Some(-122.3748889),
latitude: Some(37.6189722),
altitude: None,
relevant_text: None,
}).add_barcode(Barcode {
message: "SFOJFK JOHN APPLESEED LH451 2012-07-22T14:25-08:00".into(),
format: BarcodeFormat::PDF417,
message_encoding: "iso-8859-1".into(),
alt_text: None,
}).organization_name("Skyport Airways")
.description("Skyport Boarding Pass")
.logo_text("Skyport Airways")
.foreground_color("rgb(22, 55, 110)")
.background_color("rgb(22, 55, 110)")
.add_header_field(Field::new_with_change(
"GATE",
"gate",
"23",
"Gate changed to %@.",
)).add_primary_field(Field::new("SAN FRANCISCO", "depart", "SFO"))
.add_primary_field(Field::new("NEW YORK", "arrive", "JFK"))
.add_secondary_field(Field::new("PASSENGER", "passenger", "John Appleseed"))
.add_auxiliary_field(Field::new_with_change(
"DEPART",
"boardingTime",
"2:25 PM",
"Boarding time changed to %@.",
)).add_auxiliary_field(Field::new_with_change(
"FLIGHT",
"flightNewName",
"815",
"Flight number changed to %@",
)).add_auxiliary_field(Field::new("DESIG.", "class", "Coach"))
.add_auxiliary_field(Field::new("DATE", "date", "7/22"))
.add_back_field(Field::new("PASSPORT", "passport", "Canadian/Canadien"))
.add_back_field(Field::new(
"RESIDENCE",
"residence",
"999 Infinite Loop, Apartment 42, Cupertino CA",
)).finish_boarding_pass(TransitType::Air);
println!("{}", serde_json::to_string_pretty(&pass).unwrap());
}
#[test]
fn de_pass_example() {
use super::*;
let src = r#"
{
"formatVersion" : 1,
"passTypeIdentifier": "pass.com.sergeysova.home",
"serialNumber" : "0001",
"teamIdentifier" : "CDHE9L6U22",
"webServiceURL" : "https://example.com/passes/",
"authenticationToken" : "vxwxd7J8AlNNFPS8k0a0FfUFtq0ewzFdc",
"relevantDate" : "2012-07-22T14:25-08:00",
"locations" : [
{
"longitude" : -122.3748889,
"latitude" : 37.6189722
}
],
"barcode" : {
"message" : "SFOJFK JOHN APPLESEED LH451 2012-07-22T14:25-08:00",
"format" : "PKBarcodeFormatPDF417",
"messageEncoding" : "iso-8859-1"
},
"organizationName" : "Skyport Airways",
"description" : "Skyport Boarding Pass",
"logoText" : "Skyport Airways",
"foregroundColor" : "rgb(22, 55, 110)",
"backgroundColor" : "rgb(50, 91, 185)",
"boardingPass" : {
"transitType" : "PKTransitTypeAir",
"headerFields" : [
{
"label" : "GATE",
"key" : "gate",
"value" : "23",
"changeMessage" : "Gate changed to %@."
}
],
"primaryFields" : [
{
"key" : "depart",
"label" : "SAN FRANCISCO",
"value" : "SFO"
},
{
"key" : "arrive",
"label" : "NEW YORK",
"value" : "JFK"
}
],
"secondaryFields" : [
{
"key" : "passenger",
"label" : "PASSENGER",
"value" : "John Appleseed"
}
],
"auxiliaryFields" : [
{
"label" : "DEPART",
"key" : "boardingTime",
"value" : "2:25 PM",
"changeMessage" : "Boarding time changed to %@."
},
{
"label" : "FLIGHT",
"key" : "flightNewName",
"value" : "815",
"changeMessage" : "Flight number changed to %@"
},
{
"key" : "class",
"label" : "DESIG.",
"value" : "Coach"
},
{
"key" : "date",
"label" : "DATE",
"value" : "7/22"
}
],
"backFields" : [
{
"key" : "passport",
"label" : "PASSPORT",
"value" : "Canadian/Canadien"
},
{
"key" : "residence",
"label" : "RESIDENCE",
"value" : "999 Infinite Loop, Apartment 42, Cupertino CA"
}
]
}
}
"#;
let pass: Pass = serde_json::from_str(&src).unwrap();
println!("{:#?}", pass);
}
}