upstox-rust-sdk 0.2.0

SDK to access Upstox's Uplink v2 APIs programmatically
Documentation
use {
    crate::{
        client::ApiClient,
        constants::{
            INSTRUMENTS_ARCHIVE_FILENAME, INSTRUMENTS_COMPLETE_URL, INSTRUMENTS_JSON_FILENAME,
        },
        models::{
            instruments::instruments_response::InstrumentsResponse,
            ws::portfolio_feed_response::PortfolioFeedResponse, ExchangeSegment,
        },
        protos::market_data_feed::FeedResponse as MarketDataFeedResponse,
    },
    flate2::read::GzDecoder,
    reqwest::{Client, Response},
    std::{
        collections::HashMap,
        fs::File,
        io::{copy, Read},
    },
    tokio::fs,
    tracing::info,
};

impl<F, G> ApiClient<F, G>
where
    F: FnMut(PortfolioFeedResponse) + Send + Sync + 'static,
    G: FnMut(MarketDataFeedResponse) + Send + Sync + 'static,
{
    pub async fn get_instruments(&self) -> Result<Vec<InstrumentsResponse>, String> {
        let client: &Client = &self.client;
        let archive_path: &str = INSTRUMENTS_ARCHIVE_FILENAME;
        let json_path: &str = INSTRUMENTS_JSON_FILENAME;
        let url: &str = INSTRUMENTS_COMPLETE_URL;

        let archive_file: File = match File::open(archive_path) {
            Ok(file) => Ok(file),
            Err(_) => {
                let response: Response = client
                    .get(url)
                    .send()
                    .await
                    .map_err(|_| "Failed to fetch instruments".to_string())?;
                let bytes = response
                    .bytes()
                    .await
                    .map_err(|_| "Failed to read response bytes".to_string())?;
                fs::write(archive_path, &bytes)
                    .await
                    .map_err(|_| "Failed to write archive".to_string())?;
                File::open(archive_path).map_err(|_| "Failed to open archive".to_string())
            }
        }?;
        info!("Instruments archive downloaded");

        let mut archive: GzDecoder<File> = GzDecoder::new(archive_file);
        let mut output_file: File =
            File::create(json_path).map_err(|_| "Failed to create JSON file".to_string())?;
        copy(&mut archive, &mut output_file)
            .map_err(|_| "Failed to extract archive".to_string())?;

        fs::remove_file(archive_path)
            .await
            .expect("Failed to delete archive");

        let mut json_file: File =
            File::open(json_path).map_err(|_| "Failed to open JSON file".to_string())?;
        let mut json_content: String = String::new();
        json_file
            .read_to_string(&mut json_content)
            .map_err(|_| "Failed to read JSON file")?;
        fs::remove_file(json_path)
            .await
            .map_err(|_| "Failed to delete JSON file")?;

        let instruments_data: Vec<InstrumentsResponse> = serde_json::from_str(&json_content)
            .map_err(|_| "Failed to parse Instruments JSON".to_string())?;
        Ok(instruments_data)
    }

    pub fn parse_instruments(
        instruments: Vec<InstrumentsResponse>,
    ) -> HashMap<ExchangeSegment, HashMap<String, Vec<InstrumentsResponse>>> {
        let mut map: HashMap<ExchangeSegment, HashMap<String, Vec<InstrumentsResponse>>> =
            HashMap::new();

        for instrument in instruments {
            let (segment, instrument_type) = match &instrument {
                InstrumentsResponse::EquityResponse {
                    segment,
                    instrument_type,
                    ..
                } => (segment.clone(), instrument_type.clone()),
                InstrumentsResponse::DerivativeResponse {
                    segment,
                    instrument_type,
                    ..
                } => (segment.clone(), instrument_type.clone()),
                InstrumentsResponse::IndexResponse {
                    segment,
                    instrument_type,
                    ..
                } => (segment.clone(), instrument_type.clone()),
            };

            let segment_map: &mut HashMap<String, Vec<InstrumentsResponse>> =
                map.entry(segment).or_insert_with(HashMap::new);
            segment_map
                .entry(instrument_type)
                .or_insert_with(Vec::new)
                .push(instrument);
        }

        map
    }
}