gflights 0.2.1

Unofficial async Rust client for the Google Flights web API — search flights, price graphs, and booking offers.
Documentation
use percent_encoding::utf8_percent_encode;
use serde::{Deserialize, Serialize};
use std::time::{SystemTime, UNIX_EPOCH};

use crate::parsers::common::{RequestBody, ToRequestBody, CHARACTERS_TO_ENCODE};
use crate::parsers::constants::BATCHEXECUTE;
use anyhow::Result;

#[derive(Debug, Deserialize, Serialize)]
pub struct CityRequestOptions {
    pub city: String,
    pub frontend_version: String,
}

impl ToRequestBody for CityRequestOptions {
    fn to_request_body(&self) -> Result<RequestBody> {
        self.try_into()
    }
}

impl TryFrom<&CityRequestOptions> for RequestBody {
    type Error = anyhow::Error;
    fn try_from(options: &CityRequestOptions) -> Result<Self> {
        let epoch_now = SystemTime::now().duration_since(UNIX_EPOCH)?.as_millis();

        let body = format!(
            r#"f.req=[[["H028ib","[\"{0}\",[1,2,3,5,4],null,[1,1,1],1]",null,"generic"]]]&at=AAuQa1qqZgn5F209lkOLZp20vq5d:{1}&"#,
            &options.city, epoch_now
        );

        let url = format!("{BATCHEXECUTE}?rpcids=H028ib&source-path=/travel/flights&f.sid=-2414068248310847860&bl={}&hl=en-GB&soc-app=162&soc-platform=1&soc-device=1&_reqid=581503&rt=c",options.frontend_version);
        Ok(Self {
            url,
            body: utf8_percent_encode(&body, CHARACTERS_TO_ENCODE).to_string(),
        })
    }
}

#[cfg(test)]
#[allow(clippy::unwrap_used)]
mod tests {
    use anyhow::Ok;

    use super::*;
    #[test]
    fn test_produce_correct_body() -> Result<()> {
        let frontend_version = "boq_travel-frontend-ui_20240110.02_p0".to_string();
        let options = CityRequestOptions {
            city: "london".to_string(),
            frontend_version,
        };
        let req: RequestBody = (&options).try_into()?;
        let expected = "f.req=%5B%5B%5B%22H028ib%22%2C%22%5B%5C%22london%5C%22%2C%5B1%2C2%2C3%2C5%2C4%5D%2Cnull%2C%5B1%2C1%2C1%5D%2C1%5D%22%2Cnull%2C%22generic%22%5D%5D%5D&at=AAuQa1qqZgn5F209lkOLZp20vq5d%3A";
        assert!(req.body.starts_with(expected));

        Ok(())
    }
}