gwelle 0.1.0

Lightweight Rust client for the Google Trends API
Documentation
use crate::client::{format_req_param, TrendsClient};
use crate::models::{InterestOverTime, TimelinePoint, WidgetToken};
use crate::{Result, TrendsError};

impl TrendsClient {
    pub async fn interest_over_time(&self, token: &WidgetToken) -> Result<InterestOverTime> {
        let url = "https://trends.google.com/trends/api/widgetdata/multiline";

        // Google Trends strictly fails with 400 Bad Request if the JSON is strictly minified without spaces
        // after colons and commas. We inject them to simulate Python's default JSON serializer.
        let req_str = format_req_param(&token.request)?;

        let tz_str = self.tz.to_string();

        let params = vec![
            ("hl", self.hl.as_str()),
            ("tz", tz_str.as_str()),
            ("req", req_str.as_str()),
            ("token", token.token.as_str()),
        ];

        let result = self.get_json_with_params(url, &params).await?;

        let default_obj = result.get("default").ok_or_else(|| {
            TrendsError::TokenNotFound("Missing 'default' in multiline response".into())
        })?;

        let mut timeline = Vec::new();
        if let Some(timeline_arr) = default_obj.get("timelineData").and_then(|t| t.as_array()) {
            for item in timeline_arr {
                let time = item
                    .get("time")
                    .and_then(|v| v.as_str())
                    .unwrap_or("")
                    .to_string();
                let formatted_time = item
                    .get("formattedTime")
                    .and_then(|v| v.as_str())
                    .unwrap_or("")
                    .to_string();
                let is_partial = item
                    .get("isPartial")
                    .and_then(|v| v.as_bool())
                    .unwrap_or(false);

                let mut values = Vec::new();
                if let Some(val_arr) = item.get("value").and_then(|v| v.as_array()) {
                    for v in val_arr {
                        let raw = v.as_u64().ok_or_else(|| {
                            TrendsError::TokenNotFound(
                                "Non-numeric entry in timelineData.value".into(),
                            )
                        })?;
                        let parsed = u32::try_from(raw).map_err(|_| {
                            TrendsError::TokenNotFound(
                                "Out-of-range entry in timelineData.value".into(),
                            )
                        })?;
                        values.push(parsed);
                    }
                }

                timeline.push(TimelinePoint {
                    time,
                    formatted_time,
                    values,
                    is_partial,
                });
            }
        }

        let mut averages = Vec::new();
        if let Some(avg_arr) = default_obj.get("averages").and_then(|a| a.as_array()) {
            for v in avg_arr {
                let raw = v.as_u64().ok_or_else(|| {
                    TrendsError::TokenNotFound("Non-numeric entry in averages".into())
                })?;
                let parsed = u32::try_from(raw).map_err(|_| {
                    TrendsError::TokenNotFound("Out-of-range entry in averages".into())
                })?;
                averages.push(parsed);
            }
        }

        Ok(InterestOverTime {
            timeline_data: timeline,
            averages,
        })
    }
}