Skip to main content

cbr_client/
models.rs

1use std::fmt;
2
3use serde::de::{self, Visitor};
4use serde::{Deserialize, Deserializer, Serialize};
5
6use crate::types::{
7    CategoryId, ColumnId, DatasetId, DmyDate, ElementId, IndicatorId, InputError, IsoDateTime,
8    MeasureId, ParentRef, PeriodId, Periodicity, PublicationId, RowId, UnitId, Year,
9};
10
11/// Индикатор в ответе `/datasetsEx`.
12pub type DatasetExIndicator = NamedEntity<IndicatorId, IndicatorId>;
13
14/// Разрез `measure_1` в ответе `/datasetsEx`.
15pub type DatasetExMeasure1 = NamedEntity<MeasureId, IndicatorId>;
16
17/// Разрез `measure_2` в ответе `/datasetsEx`.
18pub type DatasetExMeasure2 = NamedEntity<MeasureId, IndicatorId>;
19
20/// Единица измерения в ответе `/datasetsEx`.
21pub type DatasetExUnit = NamedEntity<UnitId, UnitId>;
22
23/// Тип кода показателя из `/datasets`.
24#[derive(Debug, Clone, Copy, PartialEq, Eq)]
25pub enum DatasetKind {
26    /// Код `1`.
27    Code1,
28    /// Код `2`.
29    Code2,
30    /// Неизвестный код.
31    Unknown(i32),
32}
33
34impl DatasetKind {
35    /// Создаёт тип из числового кода API.
36    #[must_use]
37    #[inline]
38    pub fn from_code(value: i32) -> Self {
39        match value {
40            1 => Self::Code1,
41            2 => Self::Code2,
42            other => Self::Unknown(other),
43        }
44    }
45
46    /// Возвращает исходный числовой код.
47    #[must_use]
48    #[inline]
49    pub fn code(self) -> i32 {
50        match self {
51            Self::Code1 => 1,
52            Self::Code2 => 2,
53            Self::Unknown(value) => value,
54        }
55    }
56}
57
58impl<'de> Deserialize<'de> for DatasetKind {
59    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
60    where
61        D: Deserializer<'de>,
62    {
63        Ok(Self::from_code(i32::deserialize(deserializer)?))
64    }
65}
66
67/// Тип отчётности показателя (`reporting` в `/datasets`).
68#[derive(Debug, Clone, PartialEq, Eq)]
69pub enum DatasetReporting {
70    /// Значение `period`.
71    Period,
72    /// Любое неизвестное значение.
73    Unknown(String),
74}
75
76impl DatasetReporting {
77    /// Возвращает исходное строковое представление.
78    #[must_use]
79    #[inline]
80    pub fn as_str(&self) -> &str {
81        match self {
82            Self::Period => "period",
83            Self::Unknown(value) => value.as_str(),
84        }
85    }
86}
87
88impl<'de> Deserialize<'de> for DatasetReporting {
89    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
90    where
91        D: Deserializer<'de>,
92    {
93        let value = String::deserialize(deserializer)?;
94        Ok(match value.as_str() {
95            "period" => Self::Period,
96            _ => Self::Unknown(value),
97        })
98    }
99}
100
101/// Код типа серии из `/data`.
102#[derive(Debug, Clone, Copy, PartialEq, Eq)]
103pub enum SeriesType {
104    /// Код `1`.
105    Code1,
106    /// Код `2`.
107    Code2,
108    /// Код `3`.
109    Code3,
110    /// Неизвестный код.
111    Unknown(i32),
112}
113
114impl SeriesType {
115    /// Создаёт тип из числового кода API.
116    #[must_use]
117    #[inline]
118    pub fn from_code(value: i32) -> Self {
119        match value {
120            1 => Self::Code1,
121            2 => Self::Code2,
122            3 => Self::Code3,
123            other => Self::Unknown(other),
124        }
125    }
126
127    /// Возвращает исходный числовой код.
128    #[must_use]
129    #[inline]
130    pub fn code(self) -> i32 {
131        match self {
132            Self::Code1 => 1,
133            Self::Code2 => 2,
134            Self::Code3 => 3,
135            Self::Unknown(value) => value,
136        }
137    }
138}
139
140impl<'de> Deserialize<'de> for SeriesType {
141    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
142    where
143        D: Deserializer<'de>,
144    {
145        Ok(Self::from_code(i32::deserialize(deserializer)?))
146    }
147}
148
149/// Ключ сортировки из `DataExLink::s_sort`.
150#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
151#[serde(untagged)]
152pub enum SortKey {
153    /// Числовой ключ сортировки.
154    Numeric(i32),
155    /// Текстовый ключ сортировки.
156    Text(String),
157}
158
159impl SortKey {
160    /// Возвращает числовое значение ключа, если оно доступно.
161    #[must_use]
162    #[inline]
163    pub fn as_numeric(&self) -> Option<i32> {
164        match self {
165            Self::Numeric(value) => Some(*value),
166            Self::Text(_) => None,
167        }
168    }
169
170    /// Возвращает текстовое значение ключа, если оно нечисловое.
171    #[must_use]
172    #[inline]
173    pub fn as_text(&self) -> Option<&str> {
174        match self {
175            Self::Numeric(_) => None,
176            Self::Text(value) => Some(value.as_str()),
177        }
178    }
179}
180
181impl<'de> Deserialize<'de> for SortKey {
182    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
183    where
184        D: Deserializer<'de>,
185    {
186        struct SortKeyVisitor;
187
188        impl Visitor<'_> for SortKeyVisitor {
189            type Value = SortKey;
190
191            fn expecting(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
192                formatter.write_str("an integer or a string")
193            }
194
195            fn visit_i64<E>(self, value: i64) -> Result<Self::Value, E>
196            where
197                E: de::Error,
198            {
199                let value = i32::try_from(value)
200                    .map_err(|_| E::custom(format!("sort key is out of i32 range: {value}")))?;
201                Ok(SortKey::Numeric(value))
202            }
203
204            fn visit_u64<E>(self, value: u64) -> Result<Self::Value, E>
205            where
206                E: de::Error,
207            {
208                let value = i32::try_from(value)
209                    .map_err(|_| E::custom(format!("sort key is out of i32 range: {value}")))?;
210                Ok(SortKey::Numeric(value))
211            }
212
213            fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
214            where
215                E: de::Error,
216            {
217                match value.parse::<i32>() {
218                    Ok(parsed) => Ok(SortKey::Numeric(parsed)),
219                    Err(_) => Ok(SortKey::Text(value.to_owned())),
220                }
221            }
222
223            fn visit_string<E>(self, value: String) -> Result<Self::Value, E>
224            where
225                E: de::Error,
226            {
227                match value.parse::<i32>() {
228                    Ok(parsed) => Ok(SortKey::Numeric(parsed)),
229                    Err(_) => Ok(SortKey::Text(value)),
230                }
231            }
232        }
233
234        deserializer.deserialize_any(SortKeyVisitor)
235    }
236}
237
238/// Публикация (категория) из ответа `/publications`.
239#[derive(Debug, Clone, Deserialize, PartialEq)]
240pub struct Publication {
241    pub id: PublicationId,
242    pub parent_id: ParentRef<PublicationId>,
243    pub category_name: String,
244    #[serde(rename = "NoActive", deserialize_with = "deserialize_zero_one_bool")]
245    pub no_active: bool,
246    #[serde(default)]
247    pub pub_description: Option<String>,
248    #[serde(default)]
249    pub updated_time: Option<IsoDateTime>,
250}
251
252/// Показатель из ответа `/datasets`.
253#[derive(Debug, Clone, Deserialize, PartialEq)]
254pub struct Dataset {
255    pub id: DatasetId,
256    pub parent_id: ParentRef<PublicationId>,
257    pub name: String,
258    pub full_name: String,
259    #[serde(rename = "type")]
260    pub kind: DatasetKind,
261    pub reporting: DatasetReporting,
262    pub link: String,
263    #[serde(default)]
264    pub updated_time: Option<IsoDateTime>,
265}
266
267/// Ответ метода `/measures`.
268#[derive(Debug, Clone, Deserialize, PartialEq)]
269pub struct MeasuresResponse {
270    /// Список разрезов показателя.
271    pub measure: Vec<Measure>,
272}
273
274/// Разрез показателя.
275#[derive(Debug, Clone, Deserialize, PartialEq)]
276pub struct Measure {
277    pub id: MeasureId,
278    pub parent_id: ParentRef<DatasetId>,
279    pub name: String,
280    #[serde(default)]
281    pub sort: Option<i32>,
282}
283
284/// Диапазон годов.
285#[derive(Debug, Clone, Deserialize, PartialEq)]
286pub struct YearRange {
287    #[serde(rename = "FromYear")]
288    pub from_year: Option<Year>,
289    #[serde(rename = "ToYear")]
290    pub to_year: Option<Year>,
291}
292
293/// Расширенный справочник публикации из `/datasetsEx`.
294#[derive(Debug, Clone, Deserialize, PartialEq)]
295pub struct DatasetsExResponse {
296    pub indicators: Vec<DatasetExIndicator>,
297    #[serde(rename = "measures_1")]
298    pub measures_1: Vec<DatasetExMeasure1>,
299    #[serde(rename = "measures_2")]
300    pub measures_2: Vec<DatasetExMeasure2>,
301    pub units: Vec<DatasetExUnit>,
302    pub years: Vec<YearRange>,
303}
304
305/// Универсальная именованная сущность из `/datasetsEx`.
306#[derive(Debug, Clone, PartialEq)]
307pub struct NamedEntity<Id, ParentId> {
308    pub id: Id,
309    pub parent_id: ParentRef<ParentId>,
310    pub name: String,
311}
312
313impl<'de, Id, ParentId> Deserialize<'de> for NamedEntity<Id, ParentId>
314where
315    Id: Deserialize<'de>,
316    ParentId: TryFrom<i32, Error = InputError> + Copy,
317{
318    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
319    where
320        D: Deserializer<'de>,
321    {
322        #[derive(Deserialize)]
323        struct RawNamedEntity<Id> {
324            id: Id,
325            parent_id: i32,
326            name: String,
327        }
328
329        let raw = RawNamedEntity::deserialize(deserializer)?;
330        Ok(Self {
331            id: raw.id,
332            parent_id: ParentRef::new(raw.parent_id).map_err(serde::de::Error::custom)?,
333            name: raw.name,
334        })
335    }
336}
337
338/// Ответ метода `/data`.
339#[derive(Debug, Clone, Deserialize, PartialEq)]
340pub struct DataResponse {
341    #[serde(rename = "RawData")]
342    pub raw_data: Vec<DataRow>,
343    #[serde(rename = "headerData")]
344    pub header_data: Vec<DataHeader>,
345    pub units: Vec<DataUnit>,
346    #[serde(rename = "DTRange")]
347    pub dt_range: Vec<DataRange>,
348    #[serde(rename = "SType")]
349    pub s_type: Vec<DataSeriesType>,
350}
351
352/// Строка данных из `DataResponse::raw_data`.
353#[derive(Debug, Clone, Deserialize, PartialEq)]
354pub struct DataRow {
355    #[serde(rename = "colId")]
356    pub col_id: Option<ColumnId>,
357    pub element_id: Option<ElementId>,
358    pub measure_id: Option<MeasureId>,
359    pub unit_id: Option<UnitId>,
360    pub obs_val: Option<f64>,
361    #[serde(rename = "rowId")]
362    pub row_id: Option<RowId>,
363    pub dt: Option<String>,
364    pub periodicity: Option<Periodicity>,
365    pub date: Option<IsoDateTime>,
366    pub digits: Option<i32>,
367}
368
369/// Заголовок колонки из `DataResponse::header_data`.
370#[derive(Debug, Clone, Deserialize, PartialEq)]
371pub struct DataHeader {
372    pub id: Option<ColumnId>,
373    pub elname: Option<String>,
374}
375
376/// Единица измерения из `DataResponse::units`.
377#[derive(Debug, Clone, Deserialize, PartialEq)]
378pub struct DataUnit {
379    pub id: Option<UnitId>,
380    pub val: Option<String>,
381}
382
383/// Диапазон дат из `DataResponse::dt_range`.
384#[derive(Debug, Clone, Deserialize, PartialEq)]
385pub struct DataRange {
386    #[serde(rename = "FromY")]
387    pub from_year: Option<Year>,
388    #[serde(rename = "ToY")]
389    pub to_year: Option<Year>,
390}
391
392/// Сведения о типе серии из `DataResponse::s_type`.
393#[derive(Debug, Clone, Deserialize, PartialEq)]
394pub struct DataSeriesType {
395    #[serde(rename = "sType")]
396    pub s_type: Option<SeriesType>,
397    #[serde(rename = "dsName")]
398    pub ds_name: Option<String>,
399    #[serde(rename = "PublName")]
400    pub publ_name: Option<String>,
401}
402
403/// Ответ метода `/dataEx`.
404#[derive(Debug, Clone, Deserialize, PartialEq)]
405pub struct DataExResponse {
406    #[serde(rename = "RawData")]
407    pub raw_data: Vec<DataExRow>,
408    pub links: Vec<DataExLink>,
409}
410
411/// Строка данных из `DataExResponse::raw_data`.
412#[derive(Debug, Clone, Deserialize, PartialEq)]
413pub struct DataExRow {
414    pub indicator_id: Option<IndicatorId>,
415    pub measure_1_id: Option<MeasureId>,
416    pub measure_2_id: Option<MeasureId>,
417    pub unit_id: Option<UnitId>,
418    pub value: Option<f64>,
419    pub period_id: Option<PeriodId>,
420    pub period: Option<String>,
421    pub periodicity: Option<Periodicity>,
422    pub date: Option<DmyDate>,
423    #[serde(rename = "rowId")]
424    pub row_id: Option<RowId>,
425}
426
427/// Справочные связи из `DataExResponse::links`.
428#[derive(Debug, Clone, Deserialize, PartialEq)]
429pub struct DataExLink {
430    pub indicator_id: Option<IndicatorId>,
431    pub measure_1_id: Option<MeasureId>,
432    pub measure_2_id: Option<MeasureId>,
433    pub unit_id: Option<UnitId>,
434    pub name: Option<String>,
435    #[serde(rename = "sSort")]
436    pub s_sort: Option<SortKey>,
437}
438
439/// Описание (методология) показателя из `/DatasetDescription`.
440#[derive(Debug, Clone, Deserialize, PartialEq)]
441pub struct DatasetDescription {
442    pub description: String,
443}
444
445/// Ответ метода `/categoryNew`.
446#[derive(Debug, Clone, Deserialize, PartialEq)]
447pub struct CategoryNewResponse {
448    /// Список категорий и показателей.
449    pub category: Vec<CategoryNewItem>,
450}
451
452/// Элемент списка категорий и показателей.
453#[derive(Debug, Clone, Deserialize, PartialEq)]
454pub struct CategoryNewItem {
455    pub category_id: CategoryId,
456    pub category_name: String,
457    pub indicator_id: IndicatorId,
458    pub indicator_parent: ParentRef<IndicatorId>,
459    pub indicator_name: String,
460    pub link: String,
461    pub begin_dt: Year,
462    pub end_dt: Year,
463}
464
465/// Ответ метода `/dataNew`.
466#[derive(Debug, Clone, Deserialize, PartialEq)]
467pub struct DataNewResponse {
468    #[serde(rename = "RowData")]
469    pub row_data: Vec<DataNewRow>,
470    #[serde(rename = "Links")]
471    pub links: Vec<DataNewLink>,
472}
473
474/// Строка данных из `DataNewResponse::row_data`.
475#[derive(Debug, Clone, Deserialize, PartialEq)]
476pub struct DataNewRow {
477    pub id: Option<RowId>,
478    pub indicator_id: Option<IndicatorId>,
479    pub measure1_id: Option<MeasureId>,
480    pub measure2_id: Option<MeasureId>,
481    pub unit_id: Option<UnitId>,
482    pub obs_val: Option<f64>,
483    pub date: Option<IsoDateTime>,
484    pub periodicity: Option<Periodicity>,
485}
486
487/// Справочные связи из `DataNewResponse::links`.
488#[derive(Debug, Clone, Deserialize, PartialEq)]
489pub struct DataNewLink {
490    pub indicator_id: Option<IndicatorId>,
491    pub indicator_parent: Option<ParentRef<IndicatorId>>,
492    pub measure1_id: Option<MeasureId>,
493    pub measure2_id: Option<MeasureId>,
494    pub unit_id: Option<UnitId>,
495    pub indicator_name: Option<String>,
496    pub measure1_name: Option<String>,
497    pub measure2_name: Option<String>,
498    pub un_name: Option<String>,
499}
500
501fn deserialize_zero_one_bool<'de, D>(deserializer: D) -> Result<bool, D::Error>
502where
503    D: Deserializer<'de>,
504{
505    match i32::deserialize(deserializer)? {
506        0 => Ok(false),
507        1 => Ok(true),
508        value => Err(serde::de::Error::custom(format!(
509            "expected 0 or 1 for boolean flag, got {value}"
510        ))),
511    }
512}