1#![forbid(unsafe_code)]
2
3use serde::{Deserialize, Serialize};
4
5#[doc = "Convert to API query string (e.g., `2010:2020`)."]
15#[doc = "Fields: `page`, `pages`, `per_page`, `total`."]
25#[doc = "Prefer using `DataPoint` in downstream code."]
29#[doc = "Derives `Eq`, `Ord` so it can be used as a `BTreeMap` key."]
41#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
42pub enum DateSpec {
43 Year(i32),
45 Range { start: i32, end: i32 },
47}
48
49impl DateSpec {
50 pub fn to_query_param(&self) -> String {
51 match *self {
52 DateSpec::Year(y) => y.to_string(),
53 DateSpec::Range { start, end } => format!("{}:{}", start, end),
54 }
55 }
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
60pub struct Meta {
61 pub page: u32,
62 pub pages: u32,
63 #[serde(deserialize_with = "de_u32_from_string_or_number")]
66 pub per_page: u32,
67 pub total: u32,
68}
69
70fn de_u32_from_string_or_number<'de, D>(deserializer: D) -> Result<u32, D::Error>
72where
73 D: serde::Deserializer<'de>,
74{
75 use serde::de::{self, Visitor};
76 struct U32Visitor;
77
78 impl<'de> Visitor<'de> for U32Visitor {
79 type Value = u32;
80
81 fn expecting(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
82 write!(f, "a string or integer representing a non-negative number")
83 }
84
85 fn visit_u64<E>(self, v: u64) -> Result<Self::Value, E>
86 where
87 E: de::Error,
88 {
89 Ok(v as u32)
90 }
91
92 fn visit_i64<E>(self, v: i64) -> Result<Self::Value, E>
93 where
94 E: de::Error,
95 {
96 if v < 0 {
97 return Err(E::custom("negative value for u32"));
98 }
99 Ok(v as u32)
100 }
101
102 fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
103 where
104 E: de::Error,
105 {
106 s.parse::<u32>().map_err(E::custom)
107 }
108 }
109
110 deserializer.deserialize_any(U32Visitor)
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
114pub struct CodeName {
115 pub id: String,
116 pub value: String,
117}
118
119#[derive(Debug, Clone, Serialize, Deserialize)]
122pub struct IndicatorMeta {
123 pub id: String,
124 #[serde(alias = "value")]
125 pub name: String,
126 pub unit: Option<String>,
127}
128
129#[derive(Debug, Clone, Serialize, Deserialize)]
131pub struct Entry {
132 pub indicator: CodeName,
133 pub country: CodeName,
134 pub countryiso3code: String,
135 pub date: String,
136 pub value: Option<f64>,
137 pub unit: Option<String>,
138 #[serde(rename = "obs_status")]
139 pub obs_status: Option<String>,
140 pub decimal: Option<i32>,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
145pub struct DataPoint {
146 pub indicator_id: String,
147 pub indicator_name: String,
148 pub country_id: String, pub country_name: String,
150 pub country_iso3: String,
151 pub year: i32,
152 pub value: Option<f64>,
153 pub unit: Option<String>,
154 pub obs_status: Option<String>,
155 pub decimal: Option<i32>,
156}
157
158impl From<Entry> for DataPoint {
159 fn from(e: Entry) -> Self {
160 let year = e.date.parse::<i32>().unwrap_or(0);
161 Self {
162 indicator_id: e.indicator.id,
163 indicator_name: e.indicator.value,
164 country_id: e.country.id,
165 country_name: e.country.value,
166 country_iso3: e.countryiso3code,
167 year,
168 value: e.value,
169 unit: e.unit,
170 obs_status: e.obs_status,
171 decimal: e.decimal,
172 }
173 }
174}
175
176#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord, Hash)]
178pub struct GroupKey {
179 pub indicator_id: String,
180 pub country_iso3: String,
181}