use chrono::NaiveDate;
use serde::{Deserialize, Serialize};
use crate::{Frequency, SeasonalAdjustment, SeriesId};
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Series {
pub id: SeriesId,
pub title: String,
pub observation_start: NaiveDate,
pub observation_end: NaiveDate,
pub frequency: Frequency,
pub seasonal_adjustment: SeasonalAdjustment,
pub units: String,
pub popularity: u32,
#[serde(default)]
pub notes: Option<String>,
pub last_updated: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct SeriesSearchResults {
#[serde(rename(deserialize = "seriess", serialize = "series"))]
pub series: Vec<Series>,
pub count: u32,
pub offset: u32,
pub limit: u32,
}
#[cfg(test)]
mod tests {
use super::*;
const GNPCA_JSON: &str = r#"{
"id": "GNPCA",
"realtime_start": "2024-01-01",
"realtime_end": "2024-01-01",
"title": "Real Gross National Product",
"observation_start": "1929-01-01",
"observation_end": "2023-01-01",
"frequency": "Annual",
"frequency_short": "A",
"units": "Billions of Chained 2017 Dollars",
"units_short": "Bil. of Chn. 2017 $",
"seasonal_adjustment": "Not Seasonally Adjusted",
"seasonal_adjustment_short": "NSA",
"last_updated": "2024-03-28 07:56:03-05",
"popularity": 76,
"notes": "BEA Account Code: A001RX"
}"#;
#[test]
fn deserializes_series_metadata() {
let series: Series = serde_json::from_str(GNPCA_JSON).unwrap();
assert_eq!(series.id, SeriesId::new("GNPCA"));
assert_eq!(series.title, "Real Gross National Product");
assert_eq!(
series.observation_start,
NaiveDate::from_ymd_opt(1929, 1, 1).unwrap()
);
assert_eq!(series.frequency, Frequency::Annual);
assert_eq!(
series.seasonal_adjustment,
SeasonalAdjustment::NotSeasonallyAdjusted
);
assert_eq!(series.units, "Billions of Chained 2017 Dollars");
assert_eq!(series.popularity, 76);
assert_eq!(series.notes.as_deref(), Some("BEA Account Code: A001RX"));
}
#[test]
fn notes_default_to_none_when_absent() {
let json = r#"{
"id": "X",
"title": "T",
"observation_start": "2000-01-01",
"observation_end": "2001-01-01",
"frequency": "Monthly",
"seasonal_adjustment": "Seasonally Adjusted",
"units": "Percent",
"popularity": 0,
"last_updated": "2024-01-01 00:00:00-05"
}"#;
let series: Series = serde_json::from_str(json).unwrap();
assert_eq!(series.notes, None);
}
#[test]
fn deserializes_search_results_with_pagination() {
let json = format!(
r#"{{
"order_by": "search_rank",
"sort_order": "desc",
"count": 1,
"offset": 0,
"limit": 1000,
"seriess": [{GNPCA_JSON}]
}}"#
);
let results: SeriesSearchResults = serde_json::from_str(&json).unwrap();
assert_eq!(results.count, 1);
assert_eq!(results.offset, 0);
assert_eq!(results.limit, 1000);
assert_eq!(results.series.len(), 1);
assert_eq!(results.series[0].id, SeriesId::new("GNPCA"));
}
#[test]
fn serializes_with_typed_enum_labels_and_clean_keys() {
let series: Series = serde_json::from_str(GNPCA_JSON).unwrap();
let value = serde_json::to_value(&series).unwrap();
assert_eq!(value["id"], "GNPCA");
assert_eq!(value["frequency"], "Annual");
assert_eq!(value["seasonal_adjustment"], "Not Seasonally Adjusted");
let results = SeriesSearchResults {
series: vec![series],
count: 1,
offset: 0,
limit: 1,
};
let results_value = serde_json::to_value(&results).unwrap();
assert!(results_value.get("series").is_some());
assert!(results_value.get("seriess").is_none());
}
}