use crate::parsers::common::{decode_inner_object, decode_outer_object, get_idx};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use serde_json::Value;
use super::flight_response::{RawResponseContainer, RawResponseContainerVec};
pub fn create_raw_response_offer_vec(raw_inputs: String) -> Result<OfferRawResponseContainer> {
let outer: Vec<RawResponseContainerVec> = decode_outer_object(raw_inputs.as_ref())?;
let inner_objects: Vec<OfferRawResponse> = outer
.iter()
.flat_map(|f| &f.resp)
.filter_map(|f: &RawResponseContainer| f.payload.as_deref())
.filter_map(|payload| decode_inner_object(payload).ok())
.collect();
Ok(OfferRawResponseContainer {
response: inner_objects,
})
}
#[derive(Debug, Deserialize, Serialize)]
pub struct OfferRawResponseContainer {
pub response: Vec<OfferRawResponse>,
}
#[derive(Debug, Serialize)]
pub struct OfferRawResponse {
pub offers: Vec<OfferGroup>,
}
impl<'de> Deserialize<'de> for OfferRawResponse {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let arr = Vec::<Value>::deserialize(d)?;
let level1: Vec<Value> = get_idx(&arr, 1).unwrap_or_default();
let offer_groups: Vec<Value> = level1
.into_iter()
.filter_map(|v| v.as_array().cloned())
.flatten()
.collect();
let offers: Vec<OfferGroup> = offer_groups
.into_iter()
.filter_map(|g| serde_json::from_value(g).ok())
.collect();
Ok(OfferRawResponse { offers })
}
}
impl OfferRawResponse {
pub fn get_offer_prices(&self) -> Option<Vec<(Vec<String>, i32)>> {
let result: Vec<(Vec<String>, i32)> = self
.offers
.iter()
.filter_map(|o| o.price.map(|p| (o.airline_names.clone(), p)))
.collect();
if result.is_empty() {
None
} else {
Some(result)
}
}
}
#[derive(Debug, Serialize, Clone)]
pub struct OfferGroup {
pub airline_names: Vec<String>,
pub price: Option<i32>,
pub booking_token: Option<String>,
pub sub_options: Vec<BookingSubOption>,
pub click_token: Option<String>,
}
impl<'de> Deserialize<'de> for OfferGroup {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let arr = Vec::<Value>::deserialize(d)?;
let airlines: Vec<Vec<Value>> = get_idx(&arr, 1).unwrap_or_default();
let airline_names: Vec<String> = airlines
.iter()
.filter_map(|a| a.get(1))
.filter_map(|v| v.as_str())
.map(|s| s.to_string())
.collect();
let (price, booking_token) = [4usize, 7usize]
.into_iter()
.find_map(|idx| {
let price_arr: Vec<Value> = get_idx(&arr, idx).unwrap_or_default();
let price = price_arr
.first()
.and_then(|v| v.get(1))
.and_then(|v| v.as_i64())
.map(|v| v as i32)?;
let token = price_arr
.get(1)
.and_then(|v| v.as_str())
.map(|s| s.to_string());
Some((Some(price), token))
})
.unwrap_or((None, None));
let sub_options: Vec<BookingSubOption> = get_idx(&arr, 2).unwrap_or_default();
let url_arr: Vec<Value> = get_idx(&arr, 5).unwrap_or_default();
let click_token: Option<String> = url_arr
.get(2)
.and_then(|v| v.as_array())
.and_then(|clk| clk.get(1))
.and_then(|v| v.as_array())
.and_then(|params| params.first())
.and_then(|v| v.as_array())
.and_then(|kv| kv.get(1))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
Ok(OfferGroup {
airline_names,
price,
booking_token,
sub_options,
click_token,
})
}
}
#[derive(Debug, Serialize, Clone)]
pub struct BookingSubOption {
pub partner_names: Vec<String>,
pub price: Option<i32>,
pub booking_token: Option<String>,
pub click_token: Option<String>,
}
impl<'de> Deserialize<'de> for BookingSubOption {
fn deserialize<D: serde::Deserializer<'de>>(d: D) -> Result<Self, D::Error> {
let arr = Vec::<Value>::deserialize(d)?;
let partners: Vec<Vec<Value>> = get_idx(&arr, 1).unwrap_or_default();
let partner_names: Vec<String> = partners
.iter()
.filter_map(|p| p.get(1))
.filter_map(|v| v.as_str())
.map(|s| s.to_string())
.collect();
let url_arr: Vec<Value> = get_idx(&arr, 5).unwrap_or_default();
let click_token: Option<String> = url_arr
.get(2)
.and_then(|v| v.as_array())
.and_then(|clk| clk.get(1))
.and_then(|v| v.as_array())
.and_then(|params| params.first())
.and_then(|v| v.as_array())
.and_then(|kv| kv.get(1))
.and_then(|v| v.as_str())
.map(|s| s.to_string());
let price_arr: Vec<Value> = get_idx(&arr, 7).unwrap_or_default();
let price: Option<i32> = price_arr
.first()
.and_then(|v| v.get(1))
.and_then(|v| v.as_i64())
.map(|v| v as i32);
let booking_token: Option<String> = price_arr
.get(1)
.and_then(|v| v.as_str())
.map(|s| s.to_string());
Ok(BookingSubOption {
partner_names,
price,
booking_token,
click_token,
})
}
}
#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
use super::*;
use std::fs;
#[test]
fn test_parse_offer() -> Result<()> {
let mystr =
fs::read_to_string("test_files/offers_single_line.txt").expect("Cannot read from file");
let outer: RawResponseContainerVec = serde_json::from_str(mystr.as_ref())?;
let _inner: Result<OfferRawResponse> =
decode_inner_object(outer.resp[0].payload.as_ref().unwrap());
Ok(())
}
#[test]
fn test_parse_offer_full() -> Result<()> {
let mystr =
fs::read_to_string("test_files/offers_full.txt").expect("Cannot read from file");
let outer: Vec<RawResponseContainerVec> = decode_outer_object(mystr.as_ref())?;
let inner_objects: Vec<String> = outer
.into_iter()
.flat_map(|f| f.resp)
.flat_map(|f| f.payload)
.collect();
let inner: String = inner_objects.into_iter().next().unwrap();
let _result: Result<OfferRawResponse> = decode_inner_object(inner.as_ref());
Ok(())
}
#[test]
fn test_booking_offer_inner_payload_parses_951() -> Result<()> {
let inner_payload = r#"[
[null, [[1,2,3],null,null,null,null,[[4]]], 1, "session", "token"],
[[
[5,
[["AV","Avianca",null,true],["AA","American",null,true]],
[
[0,[["AV","Avianca",null,true]],null,[["AV","11"],["AV","44"]],false,"url",null,[[null,525],"tok_av"],null,null,false],
[0,[["AA","American",null,true]],null,[["AA","2996"],["AA","94"]],false,"url",null,[[null,426],"tok_aa"],null,null,false]
],
null,
[[null,951],"tok_combined"],
null,null,false]
]],
[["AV","11"],["AV","44"],["AA","2996"],["AA","94"]],
false,null,null,
[[null,951],"tok_repeat"]
]"#;
let result: OfferRawResponse = decode_inner_object(inner_payload)?;
let prices = result
.get_offer_prices()
.expect("expected at least one offer");
assert!(
prices.iter().any(|(_, p)| *p == 951),
"expected offer with price 951, got: {:?}",
prices
);
let offer_951 = prices.iter().find(|(_, p)| *p == 951).unwrap();
assert!(
offer_951.0.iter().any(|n| n.contains("Avianca")),
"951 offer should include Avianca, got: {:?}",
offer_951.0
);
assert!(
offer_951.0.iter().any(|n| n.contains("American")),
"951 offer should include American, got: {:?}",
offer_951.0
);
Ok(())
}
#[test]
fn test_booking_sub_option_prices() -> Result<()> {
let inner_payload = r#"[
[null, [[1,2,3],null,null,null,null,[[4]]], 1, "session", "token"],
[[
[5,
[["AV","Avianca",null,true],["AA","American",null,true]],
[
[0,[["AV","Avianca",null,true]],null,[["AV","11"],["AV","44"]],false,"url",null,[[null,525],"tok_av"]],
[0,[["AA","American",null,true]],null,[["AA","2996"],["AA","94"]],false,"url",null,[[null,426],"tok_aa"]]
],
null,
[[null,951],"tok_combined"]]
]],
false,null,null,
[[null,951],"tok_repeat"]
]"#;
let result: OfferRawResponse = decode_inner_object(inner_payload)?;
assert_eq!(result.offers.len(), 1, "expected 1 offer group");
let group = &result.offers[0];
assert_eq!(group.price, Some(951));
assert_eq!(group.sub_options.len(), 2);
assert_eq!(group.sub_options[0].price, Some(525));
assert_eq!(group.sub_options[1].price, Some(426));
Ok(())
}
}