use crate::bcbp::{Bcbp, ConditionalMetadata, Leg, SecurityData};
use crate::de::field;
use crate::error::{Error, Result};
use arrayvec::{Array, ArrayString};
use nom::{
bytes::complete::{take, take_while_m_n},
character::complete::{anychar, char},
combinator::{map, map_res},
error::{context, convert_error, ParseError, VerboseError},
sequence::tuple,
IResult,
};
fn is_ascii_uppercase_hexdigit(c: char) -> bool {
c.is_ascii_hexdigit() && !c.is_ascii_lowercase()
}
fn hex_byte_literal<'a, Error: ParseError<&'a str>>(
length: usize
) -> impl Fn(&'a str) -> IResult<&'a str, u8, Error> {
assert!(length == 1 || length == 2);
map_res(
take_while_m_n(length, length, is_ascii_uppercase_hexdigit),
|s: &str| u8::from_str_radix(s, 16),
)
}
fn variable_size_field_data<'a>(
input: &'a str,
field_id: field::Field
) -> IResult<&'a str, &'a str, VerboseError<&'a str>> {
let (remainder, length) = context(field_id.name(),
hex_byte_literal(2)
)(input)?;
match length {
0 => Ok((remainder, &input[0 .. 0])),
_ => take(length as usize)(remainder),
}
}
fn optional_variable_size_field_data<'a>(
input: &'a str,
field_id: field::Field
) -> IResult<&'a str, &'a str, VerboseError<&'a str>> {
if input.len() == 0 {
Ok((input, input))
} else {
variable_size_field_data(input, field_id)
}
}
fn number_of_legs<'a>(input: &'a str) -> IResult<&'a str, u8, VerboseError<&'a str>> {
context(field::Field::NumberOfLegsEncoded.name(),
hex_byte_literal(1)
)(input)
}
fn optional_version_number<'a>(input: &'a str) -> IResult<&'a str, Option<char>, VerboseError<&'a str>> {
if input.len() == 0 {
return Ok((input, None));
}
let (input, _) = context(field::Field::BeginningOfVersionNumber.name(),
char('>')
)(input)?;
optional_chr_field(field::Field::VersionNumber)(input)
}
fn str_field<'a, T, Error: ParseError<&'a str>>(
field_id: field::Field
) -> impl Fn(&'a str) -> IResult<&'a str, ArrayString<T>, Error>
where
T: Array<Item = u8> + Copy,
{
assert_eq!(std::mem::size_of::<T>(), field_id.len());
context(field_id.name(),
map_res(
take(field_id.len()),
|s: &str| ArrayString::from(s)
)
)
}
fn optional_str_field<'a, T, Error: ParseError<&'a str>>(
field_id: field::Field
) -> impl Fn(&'a str) -> IResult<&'a str, Option<ArrayString<T>>, Error>
where
T: Array<Item = u8> + Copy,
{
move |input: &'a str| {
if input.len() == 0 {
Ok((input, None))
} else {
map(
str_field(field_id),
|field_value| Some(field_value),
)(input)
}
}
}
fn chr_field<'a, Error: ParseError<&'a str>>(
field_id: field::Field
) -> impl Fn(&'a str) -> IResult<&'a str, char, Error> {
assert_eq!(field_id.len(), 1);
context(field_id.name(), anychar)
}
fn optional_chr_field<'a, Error: ParseError<&'a str>>(
field_id: field::Field
) -> impl Fn(&'a str) -> IResult<&'a str, Option<char>, Error> {
move |input: &'a str| {
if input.len() == 0 {
Ok((input, None))
} else {
map(
chr_field(field_id),
|c: char| Some(c)
)(input)
}
}
}
fn conditional_metadata<'a>(input: &'a str) -> IResult<&'a str, ConditionalMetadata, VerboseError<&'a str>> {
let (input, version_number) = optional_version_number(input)?;
let (remainder, conditional_item_data) =
optional_variable_size_field_data(input, field::Field::FieldSizeOfStructuredMessageUnique)?;
let (_, (
passenger_description,
source_of_check_in,
source_of_boarding_pass_issuance,
date_of_issue_of_boarding_pass,
document_type,
airline_designator_of_boarding_pass_issuer,
baggage_tag_license_plate_numbers,
first_non_consecutive_baggage_tag_license_plate_numbers,
second_non_consecutive_baggage_tag_license_plate_numbers,
)) = tuple((
optional_chr_field(field::Field::PassengerStatus),
optional_chr_field(field::Field::SourceOfCheckIn),
optional_chr_field(field::Field::SourceOfBoardingPassIssuance),
optional_str_field(field::Field::DateOfIssueOfBoardingPass),
optional_chr_field(field::Field::DocumentType),
optional_str_field(field::Field::AirlineDesignatorOfBoardingPassIssuer),
optional_str_field(field::Field::BaggageTagLicensePlateNumbers),
optional_str_field(field::Field::FirstNonConsecutiveBaggageTagLicensePlateNumbers),
optional_str_field(field::Field::SecondNonConsecutiveBaggageTagLicensePlateNumbers),
))(conditional_item_data)?;
Ok((
remainder,
ConditionalMetadata {
version_number,
passenger_description,
source_of_check_in,
source_of_boarding_pass_issuance,
date_of_issue_of_boarding_pass,
document_type,
airline_designator_of_boarding_pass_issuer,
baggage_tag_license_plate_numbers,
first_non_consecutive_baggage_tag_license_plate_numbers,
second_non_consecutive_baggage_tag_license_plate_numbers
}
))
}
fn leg<'a>(
input: &'a str,
is_first_leg: bool
) -> IResult<&'a str, (Leg, Option<ConditionalMetadata>), VerboseError<&'a str>> {
let (input, (
operating_carrier_pnr_code,
from_city_airport_code,
to_city_airport_code,
operating_carrier_designator,
flight_number,
date_of_flight,
compartment_code,
seat_number,
check_in_sequence_number,
passenger_status,
)) = tuple((
str_field(field::Field::OperatingCarrierPnrCode),
str_field(field::Field::FromCityAirportCode),
str_field(field::Field::ToCityAirportCode),
str_field(field::Field::OperatingCarrierDesignator),
str_field(field::Field::FlightNumber),
str_field(field::Field::DateOfFlight),
chr_field(field::Field::CompartmentCode),
str_field(field::Field::SeatNumber),
str_field(field::Field::CheckInSequenceNumber),
chr_field(field::Field::PassengerStatus),
))(input)?;
let (remainder, conditional_item_data) =
variable_size_field_data(input, field::Field::FieldSizeOfVariableSizeField)?;
let (conditional_item_data, optional_conditional_metadata) = if is_first_leg {
map(conditional_metadata, |data| Some(data))(conditional_item_data)?
} else {
(conditional_item_data, None)
};
let (individual_use_data, conditional_item_data) =
optional_variable_size_field_data(conditional_item_data, field::Field::FieldSizeOfStructuredMessageRepeated)?;
let (_, (
airline_numeric_code,
document_form_serial_number,
selectee_indicator,
international_document_verification,
marketing_carrier_designator,
frequent_flyer_airline_designator,
frequent_flyer_number,
id_ad_indicator,
free_baggage_allowance,
fast_track,
)) = tuple((
optional_str_field(field::Field::AirlineNumericCode),
optional_str_field(field::Field::DocumentFormSerialNumber),
optional_chr_field(field::Field::SelecteeIndicator),
optional_chr_field(field::Field::InternationalDocumentVerification),
optional_str_field(field::Field::MarketingCarrierDesignator),
optional_str_field(field::Field::FrequentFlyerAirlineDesignator),
optional_str_field(field::Field::FrequentFlyerNumber),
optional_chr_field(field::Field::IdAdIndicator),
optional_str_field(field::Field::FreeBaggageAllowance),
optional_chr_field(field::Field::FastTrack),
))(conditional_item_data)?;
let airline_individual_use = if individual_use_data.len() > 0 {
Some(String::from(individual_use_data))
} else {
None
};
let leg = Leg {
operating_carrier_pnr_code,
from_city_airport_code,
to_city_airport_code,
operating_carrier_designator,
flight_number,
date_of_flight,
compartment_code,
seat_number,
check_in_sequence_number,
passenger_status,
airline_numeric_code,
document_form_serial_number,
selectee_indicator,
international_document_verification,
marketing_carrier_designator,
frequent_flyer_airline_designator,
frequent_flyer_number,
id_ad_indicator,
free_baggage_allowance,
fast_track,
airline_individual_use,
};
Ok((remainder, (leg, optional_conditional_metadata)))
}
fn security_data<'a>(input: &'a str) -> IResult<&'a str, SecurityData, VerboseError<&'a str>> {
if input.len() == 0 {
return Ok((input, Default::default()));
}
let (input, _) = context(field::Field::BeginningOfSecurityData.name(),
char('^')
)(input)?;
let (input, type_of_security_data) =
chr_field(field::Field::TypeOfSecurityData)(input)?;
let (remainder, security_data_field_data) =
variable_size_field_data(input, field::Field::LengthOfSecurityData)?;
let security_data = if security_data_field_data.len() > 0 {
Some(String::from(security_data_field_data))
} else {
None
};
Ok((
remainder,
SecurityData {
type_of_security_data: Some(type_of_security_data),
security_data: security_data
}
))
}
fn bcbp<'a>(input: &'a str) -> IResult<&'a str, Bcbp, VerboseError<&'a str>> {
let (input, (
_,
number_of_legs_encoded,
passenger_name,
electronic_ticket_indicator,
)) = tuple((
char('M'),
number_of_legs,
str_field(field::Field::PassengerName),
chr_field(field::Field::ElectronicTicketIndicator),
))(input)?;
let mut legs = Vec::new();
let mut metadata = Default::default();
let mut input = input;
for leg_index in 0 .. number_of_legs_encoded {
let is_first_leg = leg_index == 0;
let (next_input, (current_leg, first_leg_metadata)) = leg(input, is_first_leg)?;
if let Some(value) = first_leg_metadata {
metadata = value;
}
legs.push(current_leg);
input = next_input;
}
let (remainder, security_data) = security_data(input)?;
Ok((
remainder,
Bcbp {
passenger_name,
electronic_ticket_indicator,
metadata,
legs,
security_data
},
))
}
pub fn from_str<I>(input_data: I) -> Result<Bcbp>
where
I: AsRef<str>,
{
let input = input_data.as_ref();
if !input.is_ascii() {
return Err(Error::InvalidCharacters);
}
if !input.starts_with("M") {
return Err(Error::UnsupportedFormat);
}
let (remainder, boarding_pass) = bcbp(input).map_err(|e| match e {
nom::Err::Incomplete(_) =>
Error::UnexpectedEndOfInput,
nom::Err::Error(verbose_error) | nom::Err::Failure(verbose_error) =>
Error::ParseFailed(convert_error(input, verbose_error)),
})?;
if remainder.len() > 0 {
Err(Error::TrailingCharacters)
} else {
Ok(boarding_pass)
}
}