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