use is_empty::IsEmpty;
use serde::{Deserialize, Serialize};
use super::semantic_tags::SemanticTags;
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Fields {
pub auxiliary_fields: Vec<Content>,
pub back_fields: Vec<Content>,
pub header_fields: Vec<Content>,
pub primary_fields: Vec<Content>,
pub secondary_fields: Vec<Content>,
}
impl Default for Fields {
fn default() -> Self {
Self {
auxiliary_fields: Vec::new(),
back_fields: Vec::new(),
header_fields: Vec::new(),
primary_fields: Vec::new(),
secondary_fields: Vec::new(),
}
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct Content {
pub key: String,
pub value: String,
#[serde(flatten)]
pub options: ContentOptions,
}
impl Content {
pub fn new(key: &str, value: &str, options: ContentOptions) -> Self {
Self {
key: String::from(key),
value: String::from(value),
options: options,
}
}
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub struct ContentOptions {
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub attributed_value: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub change_message: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub currency_code: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub data_detector_types: Option<DetectorType>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub date_style: Option<DateStyle>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub ignores_time_zone: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub is_relative: Option<bool>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub label: Option<String>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub number_style: Option<NumberStyle>,
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub text_alignment: Option<TextAlignment>,
#[serde(skip_serializing_if = "Option::is_none")]
pub time_style: Option<DateStyle>,
#[serde(default)]
#[serde(skip_serializing_if = "SemanticTags::is_empty")]
pub semantics: SemanticTags,
}
impl Default for ContentOptions {
fn default() -> Self {
Self {
attributed_value: None,
change_message: None,
currency_code: None,
data_detector_types: None,
date_style: None,
ignores_time_zone: None,
is_relative: None,
label: None,
number_style: None,
text_alignment: None,
time_style: None,
semantics: Default::default(),
}
}
}
#[derive(Serialize, Deserialize, Debug)]
pub enum DetectorType {
#[serde(rename = "PKDataDetectorTypePhoneNumber")]
PhoneNumber,
#[serde(rename = "PKDataDetectorTypeLink")]
Link,
#[serde(rename = "PKDataDetectorTypeAddress")]
Address,
#[serde(rename = "PKDataDetectorTypeCalendarEvent")]
CalendarEvent,
}
#[derive(Serialize, Deserialize, Debug)]
pub enum DateStyle {
#[serde(rename = "PKDateStyleNone")]
None,
#[serde(rename = "PKDateStyleShort")]
Short,
#[serde(rename = "PKDateStyleMedium")]
Medium,
#[serde(rename = "PKDateStyleLong")]
Long,
#[serde(rename = "PKDateStyleFull")]
Full,
}
#[derive(Serialize, Deserialize, Debug)]
pub enum NumberStyle {
#[serde(rename = "PKNumberStyleDecimal")]
Decimal,
#[serde(rename = "PKNumberStylePercent")]
Percent,
#[serde(rename = "PKNumberStyleScientific")]
Scientific,
#[serde(rename = "PKNumberStyleSpellOut")]
SpellOut,
}
#[derive(Serialize, Deserialize, Debug)]
pub enum TextAlignment {
#[serde(rename = "PKTextAlignmentLeft")]
Left,
#[serde(rename = "PKTextAlignmentCenter")]
Center,
#[serde(rename = "PKTextAlignmentRight")]
Right,
#[serde(rename = "PKTextAlignmentNatural")]
Natural,
}
#[derive(Serialize, Deserialize, Debug)]
#[serde(rename_all = "camelCase")]
pub enum Type {
BoardingPass {
#[serde(flatten)]
pass_fields: Fields,
#[serde(rename = "transitType")]
transit_type: TransitType,
},
Coupon {
#[serde(flatten)]
pass_fields: Fields,
},
EventTicket {
#[serde(flatten)]
pass_fields: Fields,
},
Generic {
#[serde(flatten)]
pass_fields: Fields,
},
}
#[derive(Serialize, Deserialize, Debug)]
pub enum TransitType {
#[serde(rename = "PKTransitTypeAir")]
Air,
#[serde(rename = "PKTransitTypeBoat")]
Boat,
#[serde(rename = "PKTransitTypeBus")]
Bus,
#[serde(rename = "PKTransitTypeGeneric")]
Generic,
#[serde(rename = "PKTransitTypeTrain")]
Train,
}
impl Type {
pub fn add_auxiliary_field(mut self, field: Content) -> Self {
match self {
Self::BoardingPass {
ref mut pass_fields,
transit_type: _,
} => pass_fields.auxiliary_fields.push(field),
Self::Coupon {
ref mut pass_fields,
}
| Self::EventTicket {
ref mut pass_fields,
}
| Self::Generic {
ref mut pass_fields,
} => pass_fields.auxiliary_fields.push(field),
}
self
}
pub fn add_back_field(mut self, field: Content) -> Self {
match self {
Self::BoardingPass {
ref mut pass_fields,
transit_type: _,
} => pass_fields.back_fields.push(field),
Self::Coupon {
ref mut pass_fields,
}
| Self::EventTicket {
ref mut pass_fields,
}
| Self::Generic {
ref mut pass_fields,
} => pass_fields.back_fields.push(field),
}
self
}
pub fn add_header_field(mut self, field: Content) -> Self {
match self {
Self::BoardingPass {
ref mut pass_fields,
transit_type: _,
} => pass_fields.header_fields.push(field),
Self::Coupon {
ref mut pass_fields,
}
| Self::EventTicket {
ref mut pass_fields,
}
| Self::Generic {
ref mut pass_fields,
} => pass_fields.header_fields.push(field),
}
self
}
pub fn add_primary_field(mut self, field: Content) -> Self {
match self {
Self::BoardingPass {
ref mut pass_fields,
transit_type: _,
} => pass_fields.primary_fields.push(field),
Self::Coupon {
ref mut pass_fields,
}
| Self::EventTicket {
ref mut pass_fields,
}
| Self::Generic {
ref mut pass_fields,
} => pass_fields.primary_fields.push(field),
}
self
}
pub fn add_secondary_field(mut self, field: Content) -> Self {
match self {
Self::BoardingPass {
ref mut pass_fields,
transit_type: _,
} => pass_fields.secondary_fields.push(field),
Self::Coupon {
ref mut pass_fields,
}
| Self::EventTicket {
ref mut pass_fields,
}
| Self::Generic {
ref mut pass_fields,
} => pass_fields.secondary_fields.push(field),
}
self
}
}
#[cfg(test)]
mod tests {
use crate::pass::semantic_tags::SemanticTagSeat;
use super::*;
#[test]
fn make_pass() {
let pass = Type::Generic {
pass_fields: Fields {
..Default::default()
},
};
let json = serde_json::to_string_pretty(&pass).unwrap();
println!("{}", json);
let json_expected = r#"{
"generic": {
"auxiliaryFields": [],
"backFields": [],
"headerFields": [],
"primaryFields": [],
"secondaryFields": []
}
}"#;
assert_eq!(json_expected, json);
let pass: Type = serde_json::from_str(json_expected).unwrap();
let json = serde_json::to_string_pretty(&pass).unwrap();
assert_eq!(json_expected, json);
}
#[test]
fn make_boarding_pass() {
let boarding_pass = Type::BoardingPass {
pass_fields: Fields {
..Default::default()
},
transit_type: TransitType::Air,
}
.add_primary_field(Content::new("title", "Airplane Ticket", Default::default()))
.add_primary_field(Content::new(
"seat",
"12",
ContentOptions {
semantics: SemanticTags {
seats: vec![SemanticTagSeat {
seat_number: String::from("12").into(),
seat_row: String::from("5A").into(),
seat_section: String::from("A").into(),
..Default::default()
}],
..Default::default()
},
label: String::from("Seat").into(),
..Default::default()
},
))
.add_header_field(Content::new("company", "DAL", Default::default()))
.add_header_field(Content::new(
"company_sub",
"Dodo Air Lines",
Default::default(),
))
.add_secondary_field(Content::new(
"description",
"Some information here",
Default::default(),
));
let json = serde_json::to_string_pretty(&boarding_pass).unwrap();
println!("{}", json);
let json_expected = r#"{
"boardingPass": {
"auxiliaryFields": [],
"backFields": [],
"headerFields": [
{
"key": "company",
"value": "DAL"
},
{
"key": "company_sub",
"value": "Dodo Air Lines"
}
],
"primaryFields": [
{
"key": "title",
"value": "Airplane Ticket"
},
{
"key": "seat",
"value": "12",
"label": "Seat",
"semantics": {
"seats": [
{
"seatNumber": "12",
"seatRow": "5A",
"seatSection": "A"
}
]
}
}
],
"secondaryFields": [
{
"key": "description",
"value": "Some information here"
}
],
"transitType": "PKTransitTypeAir"
}
}"#;
assert_eq!(json_expected, json);
let boarding_pass: Type = serde_json::from_str(json_expected).unwrap();
let json = serde_json::to_string_pretty(&boarding_pass).unwrap();
assert_eq!(json_expected, json);
}
#[test]
fn make_event_ticket() {
let event_ticket = Type::EventTicket {
pass_fields: Fields {
..Default::default()
},
}
.add_primary_field(Content::new(
"title",
"Super Ticket",
ContentOptions {
label: String::from("NAME").into(),
..Default::default()
},
))
.add_primary_field(Content::new("seat", "12", Default::default()))
.add_header_field(Content::new("event_title", "KKK", Default::default()))
.add_header_field(Content::new("some", "123", Default::default()))
.add_secondary_field(Content::new(
"description",
"Some information here",
Default::default(),
));
let json = serde_json::to_string_pretty(&event_ticket).unwrap();
println!("{}", json);
let json_expected = r#"{
"eventTicket": {
"auxiliaryFields": [],
"backFields": [],
"headerFields": [
{
"key": "event_title",
"value": "KKK"
},
{
"key": "some",
"value": "123"
}
],
"primaryFields": [
{
"key": "title",
"value": "Super Ticket",
"label": "NAME"
},
{
"key": "seat",
"value": "12"
}
],
"secondaryFields": [
{
"key": "description",
"value": "Some information here"
}
]
}
}"#;
assert_eq!(json_expected, json);
let event_ticket: Type = serde_json::from_str(json_expected).unwrap();
let json = serde_json::to_string_pretty(&event_ticket).unwrap();
assert_eq!(json_expected, json);
}
}